使用 PHPUnit 模拟私有方法您通常不想这样做

2022-08-30 08:11:11

我有一个关于使用PHPUnit来模拟类中的私有方法的问题。让我用一个例子来介绍:

class A {
  public function b() { 
    // some code
    $this->c(); 
    // some more code
  }

  private function c(){ 
    // some code
  }
}

我如何存根私有方法的结果来测试公共函数的更多代码部分。

解决了部分阅读问题


答案 1

通常你只是不测试或嘲笑私人和受保护的方法。

您要测试的是类的公共 API。其他所有内容都是类的实现细节,如果更改测试,则不应“中断”测试。

当您注意到“无法获得 100% 的代码覆盖率”时,这也对您有所帮助,因为您的类中可能有无法通过调用公共 API 来执行的代码。


您通常不想这样做

但是,如果您的类如下所示:

class a {

    public function b() {
        return 5 + $this->c();
    }

    private function c() {
        return mt_rand(1,3);
    }
}

我可以看到需要模拟c(),因为“随机”函数是全局状态,你不能测试它。

“干净?/冗长?/过于复杂-也许?/i-like-it-通常”解决方案

class a {

    public function __construct(RandomGenerator $foo) {
        $this->foo = $foo;
    }

    public function b() {
        return 5 + $this->c();
    }

    private function c() {
        return $this->foo->rand(1,3);
    }
}

现在不再需要模拟“c()”,因为它不包含任何全局变量,你可以很好地测试。


如果你不想做或不能从你的私有函数中删除全局状态(坏事坏现实或你对坏的定义可能不同),你可以针对模拟进行测试。

// maybe set the function protected for this to work
$testMe = $this->getMock("a", array("c"));
$testMe->expects($this->once())->method("c")->will($this->returnValue(123123));

并针对此模拟运行测试,因为您删除/模拟的唯一函数是“c()”。


引用“Pragmatic Unit Testing”一书:

“一般来说,你不想为了测试而破坏任何封装(或者像妈妈曾经说过的那样,'不要暴露你的隐私!)。大多数情况下,您应该能够通过执行类的公共方法来测试该类。如果私人或受保护的访问背后隐藏着重要的功能,这可能是一个警告信号,表明那里有另一个阶级正在努力摆脱。


更多:为什么你不想测试私有方法。


答案 2

您可以在测试中使用反射setAccessible(),以允许您设置对象的内部状态,使其从私有方法返回所需的内容。您需要使用 PHP 5.3.2。

$fixture = new MyClass(...);
$reflector = new ReflectionProperty('MyClass', 'myPrivateProperty');
$reflector->setAccessible(true);
$reflector->setValue($fixture, 'value');
// test $fixture ...

推荐