集成测试模拟facade与注入模拟


Integration tests mocking facades vs injecting mocks

我们有一些遗留的laravel项目在类中使用facade。

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;
    }
}

我们最近的项目使用了facade所表示的底层laravel类的依赖注入,正如Taylor Otwell自己所暗示的那样。(我们对每个类都使用构造函数注入,但为了保持示例简短,这里我使用方法注入并使用单个类。)

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;
    }
}

我知道正面可以被嘲笑

public function testExample()
{
    Cache::shouldReceive('get')
                ->once()
                ->with('key')
                ->andReturn('value');
    $this->visit('/users')->see('value');
}

对于单元测试工作得很好。我试图理解的问题是,这些立面是否被"全局"模拟。

例如,假设我正在编写一个集成测试(在模拟服务时测试几个相互连接的类——而不是使用实时服务的端到端测试),它在某些时候执行两个独立的类,它们包含相同的外观,使用相同的参数调用相同的方法

在这些被调用的类之间,是一些复杂的功能,这些功能可以改变使用相同参数的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

我们的现代类很容易测试,因为facade表示的底层类被注入到每个类中(在本例中,每个方法)。这意味着我可以创建两个独立的模拟,并将它们注入每个类(方法)中,以模拟不同的结果。

$legacyClass->cacheFunctionOne();
// logic that changes data returned by laravel Cache object function 'someFunction'
$legacyClass->cacheFunctionTwo();

在遗留系统中,似乎模拟的facade是"全局的",因此当facade在每个类中运行时,返回完全相同的值

我这样想对吗?

*我知道从代码架构和测试的角度来看,这个例子似乎是完全多余的,但我剥离了所有实际的功能,试图给出一些我所要求的"简单"的例子

依赖注入vs facade

依赖注入的一个主要好处是,一旦你开始将依赖注入到方法中,而不是在方法中实例化/硬编码它们,代码就会变得更加可测试。这是因为您可以从单元测试中传入依赖项,并且它们将在代码中传播。

见:http://slashnode.com/dependency-injection/

依赖注入与facade形成鲜明对比。外观是静态全局类,PHP语言不允许覆盖或替换静态类上的静态函数。Laravel facade使用mock来提供模拟功能,它们受到与上面相同的事实的限制。

集成测试的问题可能出现在您希望从非模拟缓存检索数据的地方,但是一旦您使用Facade::shouldReceive(),那么Facade::get()将被模拟缓存覆盖。反之亦然。因此,在交叉调用模拟数据和非模拟数据时,facade是不合适的。

为了用您需要的不同数据集测试您的代码,最佳实践是重构您的遗留代码以使用DI。

<

简单方法/strong>

另一种选择是在集成测试开始时调用多个Facade::shouldReceive()。确保您在集成测试中进行的每个调用都以正确的顺序拥有正确的期望数量。考虑到现有的代码库,这可能是编写测试的更快的方法。

<

困难方法/strong>

而依赖注入是编程的最佳实践。很有可能你的代码库有很多遗留类,需要花费大量的时间来重构。在这种情况下,考虑使用带有fixture的测试数据库进行端到端集成测试可能是值得的。

附录:

  • 关于Facade如何调用mock,请参见- function 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