PHP:保留静态方法并保持可测试性

时间:2013-03-23 08:01:13

标签: php unit-testing testing singleton static-methods

我的静态方法是'helper'变种,例如: convertToCamelCase(),或'get singleton'品种,例如getInstance()。无论哪种方式,我很高兴他们住在帮助班。

辅助类需要广泛使用,因此我将其加载到我的图层超类型中。现在,据我所知,只要帮助器可以注入超类型,我就可以保持测试代码的完全灵活性(除了辅助类本身)。那有意义吗?或者我忽略了什么?

从另一个角度来看......在我看来,测试代码的难度与静态方法的调用次数成比例增加,而不是与静态方法本身的实际数量成比例。通过将所有这些调用放入一个类(我的帮助器),并用mock替换该类,我正在测试没有静态调用和相关问题的代码。

(我意识到我应该努力摆脱我的单身人士,但那将是一个长期项目。)

2 个答案:

答案 0 :(得分:1)

对于像“convertToCamelCase”这样严格的辅助函数的静态类,我可能只对该函数有100%的覆盖率,然后将其视为“核心”函数,而不用担心在别处嘲笑它。无论如何,“convertToCamelCase”的模拟是什么?也许你的单元测试开始闻起来有点像集成测试,如果你做得太多,但在抽象所有内容和让你的应用程序不必要地复杂化之间总会有一些权衡。

就单身人而言,它很棘手,因为你的代码中通常有静态类的名称,所以将它与模拟对象交换出来进行测试会变得有问题。你可以做的一件事是无论你在哪里进行静态方法调用,首先要重构以这种方式调用它们:

$instance = call_user_func('MyClass::getinstance');

然后,当您增加测试覆盖率时,您可以开始替换为:

$instance = call_user_func($this->myClassName . '::getinstance');

所以 - 一旦你有了,你就可以通过改变MyClass来换掉并模仿$this->myClassName。您必须确保动态地要求或自动加载相关的php文件。

重构使用抽象工厂模式会让事情更容易测试,但你也可以随着时间的推移开始实现

答案 1 :(得分:0)

但是,如果您需要在某处模拟静态类,则可以使用Moka library进行操作。这是一个例子:

class UsersController
{

    public function main()
    {
        return json_encode(User::find(1));
    }
}

这是你可以测试它的方法:

class UsersController
{
    private $_userClass;

    public function __construct($userClass = 'User')
    {
        $this->_userClass = $userClass;
    }

    public function find($id)
    {
        return json_encode($this->_userClass::find($id));
    }
}

class UsersControllerTest extends \PHPUnit_Framework_TestCase
{
    public function testMainReturnsUser()
    {
        $userClass = Moka::stubClass(null, ['::find' => 'USER']);
        $controller = new UsersController($userClass);
        $this->assertEquals('"USER"', $controller->find(1000));
    }

    public function testMainCallsFind()
    {
        $userClass = Moka::stubClass(null, ['::find' => 'USER']);
        $controller = new UsersController($userClass);
        $controller->find(1000);
        // check that `find` was called with 100
        $this->assertEquals([1000], $userClass::$moka->report('find')[0]);
    }
}