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

Dan*_*nie 12 php methods phpunit protected abstract

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

"使用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);
}
Run Code Online (Sandbox Code Playgroud)

使用PHPUnit可以轻松地在抽象类上测试公共方法.使用上述方法可以轻松地测试正常类上的受保护方法.要测试抽象类上的受保护方法必须以某种方式...

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

你是怎么做这样的测试的?

PS:问题不在于测试受保护方法的真相(参见:白色,灰色和黑盒测试).测试受保护方法的需要取决于您的测试策略.

edo*_*ian 24

既然您要求"最佳实践",我将采取不同的方法来回答:

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

只是因为你不能意味着你应该这样做.

您想测试一个类是否有效.这意味着你可以调用它的所有函数(一切都是公共的)返回正确的值(并且可能在传入的对象上调用正确的函数)而不是其他任何函数.

你不关心这是如何在课堂上实现的.

Imho甚至伤害你为非公开的任何东西写测试有两个重要原因:

  • 时间

编写测试需要更长时间,因为您需要更多,重构也需要更长时间.如果您在类中移动代码而不更改其行为,则不需要更新其测试.测试应告诉你一切仍然有效!

  • 有意义的代码覆盖率

如果您为每个受保护的方法编写测试,则会从代码覆盖率报告中遗漏一个继承的好处:它不会告诉您哪些受保护的函数不再被调用.这是(恕我直言)一件坏事,因为你要么不正确测试所有的公共方法(为什么有一个,如果你测试每一个案件未调用的方法?),或者你真的不需要这种方法了,但由于这是"绿色",你不再考虑它.

引用PHPUnit作者

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

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

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

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

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

但是,只有在真的有必要时才这样做.

抽象类中的受保护方法将通过使用上面的应用参数测试其子项的公共api来进行测试.

  • 受保护的方法是您的班级提供的公共合同的一部分,它们肯定应该进行测试."抽象类中的受保护方法将通过测试其子项的公共api进行测试,无论如何都可以使用上面的参数进行测试." 这违反了单元测试的基础.您正在描述集成测试,它不能替代正确的单元测试覆盖率和关注点分离. (4认同)
  • 单元测试的重点不仅仅是"类是否有效",而且如果它不起作用,也知道问题在哪里.为此目的,对私有方法的测试似乎对我来说非常合理. (3认同)
  • 为什么我使用受保护的方法 我有非常复杂和长代码的类,我没有任何理由将它分成不同的类.所以我将代码分成了类中不同的受保护方法.我获得了以下利润:1)类只有那些应该对客户可见的公共方法2)我可以分别测试大型算法的每一步3)我可以通过模拟低来测试算法的高级部分高级部分使用的级别保护方法.所有这些都会导致更清晰,更短,更容易理解的测试代码. (2认同)

Dav*_*ess 5

假设:您希望在抽象类上调用具体的受保护方法.

为抽象类创建一个模拟对象,并将其传递给此修改后的形式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());
}
Run Code Online (Sandbox Code Playgroud)

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