Php 嘲笑SUT本身

Php 嘲笑SUT本身,php,unit-testing,mocking,phpunit,Php,Unit Testing,Mocking,Phpunit,我的问题是关于单元测试的。假设我们有下面的类 class X { public function p1(){ //logic $a = $this->p2(); //more logic } public function p2(){ //even more logic } } 当为p1方法编写单元测试时,我应该模拟p2方法吗 我的想法是,为p1方法编写的测试应该只执行和测试p1方法,而不是p2。但

我的问题是关于单元测试的。假设我们有下面的类

class X
{
    public function p1(){
       //logic
       $a = $this->p2();
       //more logic
    }

    public function p2(){
       //even more logic
    }
}
当为p1方法编写单元测试时,我应该模拟p2方法吗

我的想法是,为p1方法编写的测试应该只执行和测试p1方法,而不是p2。但为了实现,我应该得到一个类X的mock,并在该mock实例上调用p1方法,如下所示

$xMock = $this->getMockBuilder('\X')
    ->setMethods(array('p2'))
    ->getMock();

$xMock->expects($this->any())
    ->method('p2')
    ->will($this->returnValue($value));

$resultTobeAsserted = $xMock->p1();
不幸的是,这样做对我来说有点不公平。我和同事讨论了这个话题,它归结为如何定义SUT(测试中的系统)。 如果测试人员将正在测试的特定方法视为SUT,那么从SUT调用的其他方法将被视为依赖项,测试人员自然会想要模拟它们。另一方面,如果测试人员将整个类视为SUT,那么这些方法调用将成为测试的一部分,所以没有任何理由对它们进行模拟

这个结论正确吗?哪种想法会产生更健壮的单元测试

如果测试人员将正在测试的特定方法视为SUT,那么从SUT调用的其他方法将被视为依赖项,测试人员自然会想要模拟它们


如果有支持这种分离的参数,那么可以使用相同的参数将类重构为两个类,这正是您应该做的。

您的想法是正确的。如果您想要测试方法
p1
,您不需要关心
p2
,因为您假定它已经在另一个测试中测试过。因此,您可以只模拟/存根
p2
。但是您必须记住,您应该只模拟/存根
p2
。因此,您的示例应该更像:

$xMock = $this->getMockBuilder('\X')
    ->setMethods(array('p2'))
    ->getMock();

$xMock->expects($this->any())
    ->method('p2')
    ->will($this->returnValue($value));

$resultTobeAsserted = $xMock->p1();
当为p1方法编写单元测试时,我应该模拟p2方法吗

否。

您正在对一个类调用一个方法,并且您希望事情会发生

通过mocking p2,您可以对类的实现细节进行异常处理

不幸的是,这样做对我来说有点不公平

我说的感觉很到位

哪种想法会产生更健壮的单元测试

如果您测试一个类的可观察行为,那么您可以确保当您更改该类的实现时,该类仍然执行它应该执行的操作。这就是健壮性

如果测试一个方法并模拟该方法实现的一部分(内部方法调用),则测试一个指定的实现,如果测试失败,则不知道外部行为是否发生了更改


关于这一点,我在以下文章中有更详细的描述:

这进一步详细说明了为什么我认为测试行为而不是方法很重要

总之

PHP中的单元测试是关于测试类的可观察行为的

行为:

  • 返回值
  • 调用其他方法
  • 修改全局状态(写入文件、数据库、$GLOBALS)
测试这些东西。忽略实现细节