使用 PHPUnit 模拟私有方法您通常不想这样做
我有一个关于使用PHPUnit来模拟类中的私有方法的问题。让我用一个例子来介绍:
class A {
public function b() {
// some code
$this->c();
// some more code
}
private function c(){
// some code
}
}
我如何存根私有方法的结果来测试公共函数的更多代码部分。
我有一个关于使用PHPUnit来模拟类中的私有方法的问题。让我用一个例子来介绍:
class A {
public function b() {
// some code
$this->c();
// some more code
}
private function c(){
// some code
}
}
我如何存根私有方法的结果来测试公共函数的更多代码部分。
通常你只是不测试或嘲笑私人和受保护的方法。
您要测试的是类的公共 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”一书:
“一般来说,你不想为了测试而破坏任何封装(或者像妈妈曾经说过的那样,'不要暴露你的隐私!)。大多数情况下,您应该能够通过执行类的公共方法来测试该类。如果私人或受保护的访问背后隐藏着重要的功能,这可能是一个警告信号,表明那里有另一个阶级正在努力摆脱。
您可以在测试中使用反射和 setAccessible(),
以允许您设置对象的内部状态,使其从私有方法返回所需的内容。您需要使用 PHP 5.3.2。
$fixture = new MyClass(...);
$reflector = new ReflectionProperty('MyClass', 'myPrivateProperty');
$reflector->setAccessible(true);
$reflector->setValue($fixture, 'value');
// test $fixture ...