PHP Unit / Mockery - 模拟静态函数不工作

PHP Unit / Mockery - Mock static function not working

嘿,所以我有下面的方法,当试图模拟 Manager::getConfiguration 调用时,我无法让它工作。我尝试使用 namedMock 以及单元测试装饰器将它 运行 放在另一个线程中。它要么给我一个 class 已经存在的错误,要么实际上提取我不想要的管理器配置。

public function getBaseTypeManagerConfig() {
        $baseTypeManagerConfig = array();

        if (!empty($this->dataConfig)
                && !empty($baseType = $this->dataConfig->getBaseType())) {
            $baseTypeManagerConfig = Manager::getConfiguration($baseType);
        }

        return $baseTypeManagerConfig;
}

/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
public function testBaseTypeManagerConfig() {
         $managerMock = Mockery::namedMock('Manager', 'ManagerStub');
         $managerMock->shouldReceive('getConfiguration')->andReturn(self::MOCKED_MANAGER_DECODED_JSON);

         $listModule = Mockery::mock('GenericListModule')->makePartial();
         $listModule->shouldReceive([
                 'getBaseType' => 'events',
         ]);

         $config = \SWL\Lib\Generic\ConfigFactory::getConfig('Events_Event');
         $listModule->shouldReceive('setDataConfig')->andSet('dataConfig', $config);

         $baseTypeManagerConfig = $listModule->getBaseTypeManagerConfig();
         $this->assertEquals(array(), $baseTypeManagerConfig);
}

// Attempt at using named mock to replace 'Manager' class with mock
class ManagerStub {
    public static function getConfiguration($configType) {
        return array();
    }
}

关于为什么我不能使用这个命名模拟来替换管理器调用的任何想法?单元测试和 phpunit/mockery 的新手,欢迎任何其他指点。谢谢! :)

感谢您的提问,欢迎来到 Whosebug。

Any thoughts on why I can't use this named mock to replace the Manager call? New to unit testing and phpunit/mockery so any other pointers welcome. Thank you! :)

这可能不是关于测试框架和模拟库的问题,但最好由相关语言回答,这里 PHP,但它也可能同样适用于其他语言。

调用静态方法,如:

$baseTypeManagerConfig = Manager::getConfiguration($baseType);

因为Manager::getConfiguration是静态的,所以没有什么可以动态替换的。

因为这很常见,为什么在为此类代码编写测试时,它会成为 mocking 的问题?

由于 class 只能存在一次(根据其名称不是别名的约束,例如具有一个或多个不同名称的相同 class),特定方法也只能定义一次。

使用通用模拟框架(模拟构建器、存根构建器等),可能会出现问题。虽然可以创建经理的模拟 class:

Manager -> ManagerMock

被测代码不会神奇地改变它,例如像这样:

$baseTypeManagerConfig = ManagerMock::getConfiguration($baseType);

但保持不变:

$baseTypeManagerConfig = Manager::getConfiguration($baseType);

因此对模拟一无所知。

这可能看起来您无法模拟 Manager,但只是 Manager 处于全局静态状态并且保持不变。 Manager 的 mock 添加到其中,但是为了您的测试目的执行被测代码是没有用的。

到目前为止,还不错。拥抱一下。


此类问题的一个常见答案是使 Manager 可注入。

这可以通过 测试点 ,例如仅在允许您深入检查断言的测试中具有特定的 Manager class。

这也可能是依赖注入,即将 Manager 作为依赖注入,例如在创建对象时作为其构造函数的参数。

public function getBaseTypeManagerConfig() {
        $baseTypeManagerConfig = array();

        if (!empty($this->dataConfig)
                && !empty($baseType = $this->dataConfig->getBaseType())) {
            $configurationGettingFunction = $this->configurationGettingFunction;
            $baseTypeManagerConfig = $configurationGettingFunction($baseType);
        }

        return $baseTypeManagerConfig;
}

它将 Manager::getBaseTypeManagerConfig 的隐藏依赖转化为可见的可注入依赖。

然后在测试中,当创建被测对象 (SUT) 时,您将注入“模拟”函数而不是静态函数。

这也让 SUT 需要哪些协作者来完成它的工作变得可见。您的测试涵盖了重构。

这应该很容易做到。

Mind 认为这增加了一个间接层。虽然可能值得考虑它对您的设计有益,但也可能并非如此。

因此,人们可能会认为间接是不需要的,Manager::getConfiguration 应该保持静态,静态方法调用的使用是有意的(设计)。

然后它应该能够表示可测试的配置,因此当它保持静态时,它至少能够解决两种环境中的配置需求:开发和测试。

开发环境只是默认环境,您的代码存在并执行。

测试环境就是你测试代码的环境,例如在单元测试中。

不管你最后怎么解决,写测试的时候总会遇到这两个环境

并且只有与测试环境兼容的代码才能被测试。

所以你的目标是你的代码通常是可测试的(并且有尽可能少的模拟和存根,这是你编写的代码只是为了测试,然后你最重要的是测试你的模拟和存根,更少您要测试的代码)。

这通常也是开始为代码编写测试时的方式,这会揭示设计问题,显示代码可能无法针对不同环境移植的地方。这可以:

  • 显示缺少的配置功能(例如,通过根据需要注入数据(参数)使代码按预期工作)
  • 揭示更高层次的设计问题(例如函数和 classes 不能足够灵活地按预期工作)

这里的重要部分是您在编写测试和代码时了解问题的根源,这使您很难编写测试。

所以首先要意识到最重要的不是技术问题(为什么模拟库不支持 xyz 功能?)而是理解被测代码(它实际上在这里做什么?这是我的意图吗? ).

只有后面的部分才允许您更改代码并进一步开发并从编写测试中获得好处。

否则你会写一个测试只是为了写一个并让它通过。那个用处不大。使用能够实际覆盖 Manager::getConfiguration - UopzComponere.

的扩展非常容易

但是,在这里将其视为非技术问题,而不是代码的设计问题。为什么?因为你从测试中获得了更多的收益,你的代码是要改的。现在和将来。测试允许您现在以可重现的方式查看这些更改需求,不仅是现在,而且在您开始编写测试时提前。

使用测试来定义代码应该如何工作。不创建模拟。

如果你允许我发表个人评论:我讨厌在测试中编写模拟。即使对于技术上模拟可以工作的代码,它也很早就开始变得麻烦了。因此,我经常看看如何简化代码,以便更容易使用。这样也可以更容易地进行测试。