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

Best practices to test protected methods with PHPUnit (on abstract classes)

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

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

1
2
3
4
5
6
7
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:问题不在于测试受保护方法的真相(参见:白色,灰色和黑盒测试)。 测试受保护方法的需要取决于您的测试策略。


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

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

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

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

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

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

  • 时间

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

  • 有意义的代码覆盖率

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

引用PHPUnit作者

So: Just because the testing of protected and private attributes and methods is possible does not mean that this is a"good thing".

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

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

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

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

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

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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()来决定如何构造对象。