集成测试模拟外观与注入模拟
Integration tests mocking facades vs injecting mocks
我们有一些遗留的 laravel 项目在 classes 中使用外观。
use Cache;
LegacyClass
{
public function cacheFunctionOne()
{
$result = Cache::someFunction('parameter');
// logic to manipulate result
return $result;
}
public function cacheFunctionTwo()
{
$result = Cache::someFunction('parameter');
// different logic to manipulate result
return $result;
}
}
我们最近的项目使用了底层 laravel classes 的依赖注入,外观表示已经 hinted at by Taylor Otwell himself。 (我们为每个 class 使用构造函数注入,但为了使示例简短,这里我使用方法注入并使用单个 class。)
use Illuminate\Cache\Repository as Cache;
ModernClass
{
public function cacheFunctionOne(Cache $cache)
{
$result = $cache->someFunction('parameter');
// logic to manipulate result
return $result;
}
public function cacheFunctionTwo(Cache $cache)
{
$result = $cache->someFunction('parameter');
// different logic to manipulate result
return $result;
}
}
我知道立面can be mocked
public function testExample()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
这非常适合 单元测试 。我想了解的问题是这些外墙是否被嘲笑 'globally'.
例如,假设我正在编写一个集成测试(在模拟服务时测试一些相互关联的 classes - 而不是使用实时服务的端到端测试),它在某个时候执行两个单独的 classes 其中包含调用具有相同参数的相同方法的相同外观。
在这些被调用的 class 之间,是一些复杂的功能,它使用相同的参数更改 facades 方法返回的数据。*
$modernClass->cacheFunctionOne($cache); // easily mocked
// logic that changes data returned by laravel Cache object function 'someFunction'
$modernClass->cacheFunctionTwo($cache); // easily mocked with a different mock
我们现代的 classes 很容易测试,因为 facade 代表的底层 class 被注入每个 class(在这个例子中,每个方法)。这意味着我可以创建两个单独的模拟并将它们注入每个 class (方法)以模拟不同的结果。
$legacyClass->cacheFunctionOne();
// logic that changes data returned by laravel Cache object function 'someFunction'
$legacyClass->cacheFunctionTwo();
不过在遗留系统中,似乎 模拟外观是 'global',因此当每个 [=50] 中的外观是 运行 时=], 返回完全相同的值.
我这样想对吗?
*我理解这个示例从代码架构和测试的角度来看可能看起来完全多余,但我正在剥离所有实际功能以尝试给出某种 'simple' 示例我问的是什么。
依赖注入与 Facades
依赖注入的主要好处之一是,一旦您开始将依赖项注入方法而不是 instantiating/hardcoding 在方法中注入依赖项,代码将变得更加可测试。这是因为您可以从单元测试内部传递依赖项,它们将通过代码传播。
参见:http://slashnode.com/dependency-injection/
依赖注入与门面形成鲜明对比。 Facades 是静态全局 类,PHP 语言不允许在静态 类 上覆盖或替换静态函数。 Laravel 门面使用 Mockery 来提供模拟功能,并且它们受到与上述相同的事实的限制。
集成测试的问题可能出现在您希望从非模拟缓存中检索数据但是一旦您使用 Facade::shouldReceive() 然后 Facade::get() 将被模拟覆盖缓存。反之亦然。因此,Facades 不适合交错调用模拟数据和非模拟数据的地方。
为了使用您需要的不同数据集测试您的代码,最佳做法是重构您的遗留代码以使用 DI。
集成测试
更简单的方法
另一种方法是在集成测试开始时以预期调用多个 Facade::shouldReceive()。确保您在集成测试中进行的每个调用都以正确的顺序获得正确数量的期望。鉴于您现有的代码库,这可能是编写测试的更快方法。
更难的方法
虽然依赖注入是编程的最佳实践。很可能您的代码库有太多遗留问题 类,以至于重构需要花费令人难以置信的时间。在这种情况下,可能值得考虑使用带有固定装置的测试数据库进行端到端集成测试。
附录:
- Facade 调用 mockery 的方法见函数 createMockByName(): https://github.com/laravel/framework/blob/5.3/src/Illuminate/Support/Facades/Facade.php
- 有关链接模拟调用的示例,请参阅
fhinkel commented on Feb 6, 2015
:https://github.com/padraic/mockery/issues/401
我们有一些遗留的 laravel 项目在 classes 中使用外观。
use Cache;
LegacyClass
{
public function cacheFunctionOne()
{
$result = Cache::someFunction('parameter');
// logic to manipulate result
return $result;
}
public function cacheFunctionTwo()
{
$result = Cache::someFunction('parameter');
// different logic to manipulate result
return $result;
}
}
我们最近的项目使用了底层 laravel classes 的依赖注入,外观表示已经 hinted at by Taylor Otwell himself。 (我们为每个 class 使用构造函数注入,但为了使示例简短,这里我使用方法注入并使用单个 class。)
use Illuminate\Cache\Repository as Cache;
ModernClass
{
public function cacheFunctionOne(Cache $cache)
{
$result = $cache->someFunction('parameter');
// logic to manipulate result
return $result;
}
public function cacheFunctionTwo(Cache $cache)
{
$result = $cache->someFunction('parameter');
// different logic to manipulate result
return $result;
}
}
我知道立面can be mocked
public function testExample()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
这非常适合 单元测试 。我想了解的问题是这些外墙是否被嘲笑 'globally'.
例如,假设我正在编写一个集成测试(在模拟服务时测试一些相互关联的 classes - 而不是使用实时服务的端到端测试),它在某个时候执行两个单独的 classes 其中包含调用具有相同参数的相同方法的相同外观。
在这些被调用的 class 之间,是一些复杂的功能,它使用相同的参数更改 facades 方法返回的数据。*
$modernClass->cacheFunctionOne($cache); // easily mocked
// logic that changes data returned by laravel Cache object function 'someFunction'
$modernClass->cacheFunctionTwo($cache); // easily mocked with a different mock
我们现代的 classes 很容易测试,因为 facade 代表的底层 class 被注入每个 class(在这个例子中,每个方法)。这意味着我可以创建两个单独的模拟并将它们注入每个 class (方法)以模拟不同的结果。
$legacyClass->cacheFunctionOne();
// logic that changes data returned by laravel Cache object function 'someFunction'
$legacyClass->cacheFunctionTwo();
不过在遗留系统中,似乎 模拟外观是 'global',因此当每个 [=50] 中的外观是 运行 时=], 返回完全相同的值.
我这样想对吗?
*我理解这个示例从代码架构和测试的角度来看可能看起来完全多余,但我正在剥离所有实际功能以尝试给出某种 'simple' 示例我问的是什么。
依赖注入与 Facades
依赖注入的主要好处之一是,一旦您开始将依赖项注入方法而不是 instantiating/hardcoding 在方法中注入依赖项,代码将变得更加可测试。这是因为您可以从单元测试内部传递依赖项,它们将通过代码传播。
参见:http://slashnode.com/dependency-injection/
依赖注入与门面形成鲜明对比。 Facades 是静态全局 类,PHP 语言不允许在静态 类 上覆盖或替换静态函数。 Laravel 门面使用 Mockery 来提供模拟功能,并且它们受到与上述相同的事实的限制。
集成测试的问题可能出现在您希望从非模拟缓存中检索数据但是一旦您使用 Facade::shouldReceive() 然后 Facade::get() 将被模拟覆盖缓存。反之亦然。因此,Facades 不适合交错调用模拟数据和非模拟数据的地方。
为了使用您需要的不同数据集测试您的代码,最佳做法是重构您的遗留代码以使用 DI。
集成测试
更简单的方法
另一种方法是在集成测试开始时以预期调用多个 Facade::shouldReceive()。确保您在集成测试中进行的每个调用都以正确的顺序获得正确数量的期望。鉴于您现有的代码库,这可能是编写测试的更快方法。
更难的方法
虽然依赖注入是编程的最佳实践。很可能您的代码库有太多遗留问题 类,以至于重构需要花费令人难以置信的时间。在这种情况下,可能值得考虑使用带有固定装置的测试数据库进行端到端集成测试。
附录:
- Facade 调用 mockery 的方法见函数 createMockByName(): https://github.com/laravel/framework/blob/5.3/src/Illuminate/Support/Facades/Facade.php
- 有关链接模拟调用的示例,请参阅
fhinkel commented on Feb 6, 2015
:https://github.com/padraic/mockery/issues/401