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

痞子三分冷 提交于 2019-11-28 18:01:34
edorian

Since you are asking for a 'best practice' I'll take a different approach to answer:

Don't test protected and private methods

Just because you can doesn't mean you should.

You want to test that a class works. That means that all the functions you can call on it (everything public) return the right values (and maybe call the right functions on objects passed in) and nothing else.

You don't care how this is implemented in the class.

Imho it is even hurting you to write test for anything non-public for two big reasons:

  • Time

Writing tests takes longer as you need more and refactoring also takes longer. If you move around code in a class without changing its behavior you shoudn't be required to update its tests. The tests should tell you that everything still works!

  • Meaningful code coverage

If you write a test for every protected method you loose one inherit benefit from the code coverage report: It won't tell you which protected functions aren't call anymore. That is (imho) a bad thing because you either don't test all the public methods correctly (why is there a method that isn't called if you test every case?) or you really don't need that method anymore but since it's "green" you don't give it a second thought.

To quote the PHPUnit Author

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

Since the real world sometimes differs

...->setAccessible() is fine for normal methods

for abstract stuff use ...->getMockForAbstractClass()

But please do so only if it is really necessary.

A protected method in an abstract class will get tested by testing the public api of its children anyway with my arguments from above applying.

Assumption: You want to call concrete protected methods on an abstract class.

Create a mock object for the abstract class, and pass it to this modified form of 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());
}

You could encapsulate this into a single method, but I prefer to pass in the object so that a test can call multiple protected/private methods on the same object. To do so, use $class->isAbstract() to decide how to construct the object.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!