PHPUnit模拟对象和静态方法

rr.*_*rr. 46 php phpunit unit-testing doctrine mocking

我正在寻找测试以下静态方法的最佳方法(特别是使用Doctrine模型):

class Model_User extends Doctrine_Record
{
    public static function create($userData)
    {
        $newUser = new self();
        $newUser->fromArray($userData);
        $newUser->save();
    }
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我会使用模拟对象来确保调用"fromArray"(使用提供的用户数据)和"save",但这是不可能的,因为该方法是静态的.

有什么建议?

Gor*_*don 43

PHPUnit的作者Sebastian Bergmann最近发表了一篇关于Stubbing和Mocking Static Methods的博客文章.使用PHPUnit 3.5和PHP 5.3以及后期静态绑定的一致使用,你可以做到

$class::staticExpects($this->any())
      ->method('helper')
      ->will($this->returnValue('bar'));
Run Code Online (Sandbox Code Playgroud)

更新: staticExpects被弃用的PHPUnit 3.8的,将被更高版本的完全去除.

  • 一个很好的例子,说明无用的“优化”如何把现实世界中的事情搞得一团糟。staticExpects() 是一个运行良好的工具,帮助数百万人编写了正确的测试,但后来一些“聪明”的人决定删除它,因为他们知道没有其他选择,人们只能删除测试。即使现在,七年多过去了,主要的框架和库仍然依赖于大量的静态调用,而这些静态调用现在是不可测试的。总结一下:我们想要编写测试并模拟外部库,但不能,因为 PHPUnit 的开发人员决定删除它。结果:根本没有测试。谢谢 (17认同)
  • 值得注意的是"这种方法只适用于静态方法调用的存根和模拟,其中调用者和被调用者属于同一类.这是因为[静态方法对可测试性而言是死亡](http://misko.hevery.com/2008/一十五分之一十二/静态方法-是死亡至可测试性/)". (11认同)
  • 我们知道`staticExpects`已经从最近版本的PHPUnit中删除了,那么在没有`staticExpects`的情况下实现这个目标的替代方法是什么? (5认同)
  • 从 PHPUnit v4 开始,`staticExpects` 函数已被移除。有关原因的解释,请参阅 [github 上的此线程](https://github.com/sebastianbergmann/phpunit-mock-objects/issues/137)。 (2认同)

tre*_*ace 11

现在有AspectMock库来帮助解决这个问题:

https://github.com/Codeception/AspectMock

$this->assertEquals('users', UserModel::tableName());   
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName'); 
Run Code Online (Sandbox Code Playgroud)

  • 这个图书馆是黄金!但我认为他们应该在他们的页面上放置一个免责声明:"只是因为你可以用我们的库测试全局函数和静态方法,这并不意味着你应该用这种方式编写新的代码." 我在某个地方读到了一个糟糕的测试比完全没有测试更好,并且通过这个库,您可以为遗留代码添加安全网.只要确保以更好的方式编写新代码:) (9认同)

b01*_*b01 8

我将在单元测试命名空间中创建一个新类来扩展Model_User并测试它。这是一个例子:

原班:

class Model_User extends Doctrine_Record
{
    public static function create($userData)
    {
        $newUser = new self();
        $newUser->fromArray($userData);
        $newUser->save();
    }
}
Run Code Online (Sandbox Code Playgroud)

在单元测试中调用的模拟类:

use \Model_User
class Mock_Model_User extends Model_User
{
    /** \PHPUnit\Framework\TestCase */
    public static $test;

    // This class inherits all the original classes functions.
    // However, you can override the methods and use the $test property
    // to perform some assertions.
}
Run Code Online (Sandbox Code Playgroud)

在您的单元测试中:

use Module_User;
use PHPUnit\Framework\TestCase;

class Model_UserTest extends TestCase
{
    function testCanInitialize()
    {   
        $userDataFixture = []; // Made an assumption user data would be an array.
        $sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing.

        $sut::test = $this; // This is just here to show possibilities.

        $this->assertInstanceOf(Model_User::class, $sut);
    }
}
Run Code Online (Sandbox Code Playgroud)