模拟全局静态方法的最佳方式


best way to mock a global static method

我的应用程序有一个带有签名的记录器库:

final class Logger {
  public static method debug($msg);
  public static method warn($msg);
  public static method error($msg);
}

我要测试的类,另一个全局静态帮助程序,将其用作

final class TestMe {
  public static method TestThis(){
    Logger::debug('TestThis() called');
    doThings();
  }
}

如何通过模拟Logger类并等待预期消息来测试TestMe类?

PHPUnit不能嘲笑Logger类,原因如下。

  1. 该类被列为final,这意味着它不能被扩展。当PHPUnit创建一个对象的mock时,它会创建一个新对象来扩展被mock的类。final关键字阻止扩展类,因此无法创建mock
  2. 要替换的方法是静态调用的。您无法替换它们,因为调用被定向到实际的类。PHPUnit 5.3有一种"嘲弄"静态方法的方法,但这只是在被静态调用的类中。它不会替换来自类外部的调用

http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/

你有两个选项来测试这样的东西:

  1. 增加正在测试的行为的范围。断言Logger类正在做的任何事情都已完成。这将使任何测试都不再是"单元"测试,但它们确实封装了预期的行为,并且仍然有用
  2. 重构您的用法以使用依赖项注入,这样您就可以传入一个不静态调用方法的mockLogger。这可能会更痛苦,但会使您的代码更加灵活

如果您使用像PHPUnit这样的测试框架,它提供了模拟对象的能力。您可以为记录器类创建一个mock对象,并在其中定义调试方法

这里有详细解释:

https://phpunit.de/manual/current/en/test-doubles.html

下面是一个从该页面上截取的小例子:

<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMockBuilder('SomeClass')
                     ->getMock();
        // Configure the stub.
        $stub->method('doSomething')
             ->willReturn('foo');
        // Calling $stub->doSomething() will now return
        // 'foo'.
        $this->assertEquals('foo', $stub->doSomething());
    }
}
?>