使用 PHPUnit 测试受保护方法的最佳实践(在抽象类上)

Posted

技术标签:

【中文标题】使用 PHPUnit 测试受保护方法的最佳实践(在抽象类上)【英文标题】:Best practices to test protected methods with PHPUnit (on abstract classes) 【发布时间】:2011-06-27 23:22:18 【问题描述】:

使用 phpUnit 和 PHP >= 5.3 可以测试受保护的方法。 *** 的以下页面概述了它的最佳实践:

"Best practices to test protected methods with PHPUnit"

protected static function callProtectedMethod($name, $classname, $params) 
  $class = new ReflectionClass($classname);
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  $obj = new $classname($params);
  return $method->invokeArgs($obj, $params);

使用 PHPUnit 测试抽象类的公共方法很容易。 使用上述方法很容易在普通类上测试受保护的方法。 必须以某种方式测试抽象类上的受保护方法......

我知道 PHPUnit 在具体类中派生抽象类和“实现”抽象方法,并针对该具体类触发测试 - 但我不知道如何将其集成到上述方法中以获得 callProtectedMethodOnAbstractClasses()。

你是怎么做这些测试的?

PS:问题不在于测试受保护方法的真实性(请参阅:白盒测试、灰盒测试和黑盒测试)。测试受保护方法的需求取决于您的测试策略。

【问题讨论】:

【参考方案1】:

假设:您想在抽象类上调用具体受保护的方法。

为抽象类创建一个模拟对象,并将其传递给 callProtectedMethod() 的修改形式。

public static function callProtectedMethod($object, $method, array $args=array()) 
    $class = new ReflectionClass(get_class($object));
    $method = $class->getMethod($method);
    $method->setAccessible(true);
    return $method->invokeArgs($object, $args);


public function testGetArea() 
    $rect = $this->getMockForAbstractClass('RandomRectangle');
    self::callProtectedMethod($rect, 'setWidth', array(7));
    self::callProtectedMethod($rect, 'setHeight', array(3));
    self::assertEquals(21, $rect->getArea());

您可以将其封装到单个方法中,但我更喜欢传入对象,以便测试可以在同一对象上调用多个受保护/私有方法。为此,请使用$class->isAbstract() 来决定如何构造对象。

【讨论】:

【参考方案2】:

由于您要求“最佳实践”,我将采用不同的方法来回答:

不要测试受保护的和私有的方法

仅仅因为你可以并不意味着你应该。

您想测试一个类是否有效。这意味着所有您可以调用的函数(所有公开的)返回正确的值(并且可能在传入的对象上调用正确的函数)什么都没有否则

你不在乎这在课堂上是如何实现的。

恕我直言,为任何非公开内容编写测试甚至会伤害您,原因有两个:

时间

编写测试需要更长的时间,因为您需要更多,重构也需要更长的时间。如果你在一个类中移动代码而不改变它的行为,你不应该被要求更新它的测试。测试应该告诉你一切仍然有效!

有意义的代码覆盖率

如果您为每个受保护的方法编写测试,您就会失去一个从代码覆盖率报告中继承的好处:它不会告诉您哪些受保护的函数不再被调用。那是(恕我直言)一件坏事,因为您要么没有正确测试所有公共方法(如果您测试每个案例,为什么会有一个不被调用的方法?)或者您真的 不需要该方法不再是,但既然它是“绿色”的,你就不用再考虑了。

引用 PHPUnit 作者

所以:仅仅因为受保护和私有属性和方法的测试是可能的,并不意味着这是一件“好事”。

http://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html

因为现实世界有时会有所不同

...->setAccessible() 适用于普通方法

对于抽象的东西使用...->getMockForAbstractClass()

但请仅在确实必要时这样做。

抽象类中的受保护方法将通过测试其子级的公共 api 来进行测试,并应用我上面的论点。

【讨论】:

单元测试的重点不仅是知道“类是否有效”,而且如果它不起作用,还要知道问题出在哪里。为此目的,对私有方法的测试对我来说似乎是非常合理的。 @Petr 我不认为大幅增加的维护成本以及测试套件的质量下降不值得花时间为您节省调试一两个私人电话的时间。也许如果您不为直接测试私有方法的测试生成代码覆盖率(因此您的真实测试仍在测试行为),它可能会奏效。到目前为止,我从未与在更大的环境中测试私有方法并且没有严重失败的人交谈过(最终得到了一种无用的测试套件,当进行任何最小的重构时总是必须更改它) 为什么我使用受保护的方法。我有非常复杂和长代码的类,我没有任何理由将它分成不同的类。因此,我将代码拆分为类中不同的受保护方法。而且我得到了以下利润:1)类只有那些对其客户应该可见的公共方法2)我可以单独测试大型算法的每一步,3)我可以通过模拟低级别来测试算法的高级部分高级部分使用的级保护方法。所有这些都会导致更清晰、更短且更易于理解的测试代码。 受保护的方法是您的班级提供的公共合同的一部分,绝对应该对其进行测试。 “无论如何,抽象类中的受保护方法将通过测试其子级的公共 api 来测试我上面应用的论点。”这违反了单元测试的基础。您正在描述集成测试,它不能替代适当的单元测试覆盖率和关注点分离。 我了解测试受保护方法时的“设计风格”问题。然而,就像@PetrPeller 和其他人一样,现实生活中的偶然事件并不关心纯粹的设计。例如,我有一个 300 行长的方法(!),重构为 10 个,清晰划分的块或“步骤”。由于每个块只被前一个“超级方法”调用一次,我将它们设为受保护的方法。我想对每个明确划分的块进行单元测试。我有数千个测试,再添加 10 个不会让我破产或其他什么。实际上,最终他们将我的部署速度提高了 3 倍以上!

以上是关于使用 PHPUnit 测试受保护方法的最佳实践(在抽象类上)的主要内容,如果未能解决你的问题,请参考以下文章

使用 PHPUnit 测试受保护方法的最佳实践

PHPUnit测试使用pdo的受保护静态方法

如何通过phpunit对一个方法进行单元测试,该方法具有多个内部调用保护/私有方法?

PHPUnit中的测试方法应该如何命名

使用 PHPUnit 对具有多种用户类型的网站进行单元测试的最佳方法

无法在对受保护方法进行单元测试的适当方法之间做出决定