使用PHPUnittesting受保护方法的最佳实践

我发现讨论你testing私人方法信息。

我已经决定,在一些课上,我想要有保护的方法,但是要testing它们。 其中一些方法是静态的和简短的。 因为大多数公共方法都使用它们,所以我可能会安全地移除testing。 但是,从TDD方法开始,避免debugging,我真的想testing它们。

我想到了以下几点:

  • 方法对象在build议答案似乎是矫枉过正的。
  • 从公开方法开始,当代码覆盖率由更高级别的testing给出时,将它们保护起来并移除testing。
  • inheritance带有可testing接口的类,使受保护的方法公开

哪个是最佳做法? 还有别的事吗?

看来,JUnit会自动将受保护的方法更改为公开的,但我没有更深入的了解它。 PHP不允许通过reflection 。

如果您将PHP5(> = 5.3.2)与PHPUnit一起使用,则可以使用reflection将它们设置为在运行testing之前公开,以testing您的私有和受保护的方法:

protected static function getMethod($name) { $class = new ReflectionClass('MyClass'); $method = $class->getMethod($name); $method->setAccessible(true); return $method; } public function testFoo() { $foo = self::getMethod('foo'); $obj = new MyClass(); $foo->invokeArgs($obj, array(...)); ... } 

你似乎已经知道了,但我只是重申它; 这是一个坏的迹象,如果你需要testing受保护的方法。 unit testing的目的是testing一个类的接口,受保护的方法是实现细节。 也就是说,有些情况是有道理的。 如果使用inheritance,则可以将超类看作为子类提供接口。 所以在这里,你将不得不testing受保护的方法(但从来没有一个私人的 )。 解决这个问题的方法是创build一个用于testing目的的子类,并使用它来公开方法。 例如。:

 class Foo { protected function stuff() { // secret stuff, you want to test } } class SubFoo extends Foo { public function exposedStuff() { return $this->stuff(); } } 

请注意,您始终可以用组合来replaceinheritance。 在testing代​​码时,处理使用此模式的代码通常要容易得多,因此您可能需要考虑该选项。

teastburn有正确的做法。 更简单的是直接调用方法并返回答案:

 class PHPUnitUtil { public static function callMethod($obj, $name, array $args) { $class = new \ReflectionClass($obj); $method = $class->getMethod($name); $method->setAccessible(true); return $method->invokeArgs($obj, $args); } } 

您可以通过以下方式简单地在您的testing

 $returnVal = PHPUnitUtil::callMethod( $this->object, '_nameOfProtectedMethod', array($arg1, $arg2) ); 

我想build议在uckelman的答案中定义getMethod()的一个细微变化。

此版本通过删除硬编码值并简化了一些使用来更改getMethod()。 我build议像下面的例子那样将它添加到你的PHPUnitUtil类中,或者把它添加到你的PHPUnit_Framework_TestCase扩展类中(或者,我想是全局的PHPUnitUtil文件)。

由于MyClass正在被实例化,并且ReflectionClass可以接受一个string或一个对象…

 class PHPUnitUtil { /** * Get a private or protected method for testing/documentation purposes. * How to use for MyClass->foo(): * $cls = new MyClass(); * $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo'); * $foo->invoke($cls, $...); * @param object $obj The instantiated instance of your class * @param string $name The name of your private/protected method * @return ReflectionMethod The method you asked for */ public static function getPrivateMethod($obj, $name) { $class = new ReflectionClass($obj); $method = $class->getMethod($name); $method->setAccessible(true); return $method; } // ... some other functions } 

我还创build了一个别名函数getProtectedMethod()来明确预期的内容,但是这取决于您。

干杯!

我觉得他们很接近。 我会这样做:

 class ClassToTest { protected testThisMethod() { // Implement stuff here } } 

然后,执行这样的事情:

 class TestClassToTest extends ClassToTest { public testThisMethod() { return parent::testThisMethod(); } } 

然后,您对TestClassToTest运行testing。

应该可以通过parsing代码自动生成这样的扩展类。 如果PHPUnit已经提供了这种机制(尽pipe我没有检查),我不会感到惊讶。

我要在这里把我的帽子扔进戒指

我已经使用了__call黑客成功的混合程度。 我想出的另一种方法是使用访问者模式:

1:生成一个stdClass或自定义类(强制types)

2:用所需的方法和参数来引用

3:确保你的SUT有一个acceptVisitor方法,它将使用在访问类中指定的参数来执行方法

4:将其注入您想要testing的课程中

5:SUT将操作的结果注入访问者

6:将testing条件应用于访问者的结果属性

你确实可以通用的方式使用__call()来访问受保护的方法。 为了能够testing这个类

 class Example { protected function getMessage() { return 'hello'; } } 

你在ExampleTest.php中创build一个子类:

 class ExampleExposed extends Example { public function __call($method, array $args = array()) { if (!method_exists($this, $method)) throw new BadMethodCallException("method '$method' does not exist"); return call_user_func_array(array($this, $method), $args); } } 

请注意,__call()方法不会以任何方式引用该类,因此您可以使用要testing的受保护方法复制上面的每个类,只需更改类声明即可。 你可以把这个函数放在一个通用的基类中,但是我没有尝试过。

现在,testing用例本身只是在构build要testing的对象的位置上有所不同,请参阅ExampleExposed for Example。

 class ExampleTest extends PHPUnit_Framework_TestCase { function testGetMessage() { $fixture = new ExampleExposed(); self::assertEquals('hello', $fixture->getMessage()); } } 

我相信PHP 5.3允许你使用reflection直接改变方法的可访问性,但是我认为你必须为每个方法分别进行改变。

我build议下面的解决方法“Henrik Paul”的解决方法/想法:)

你知道你的class级的私人方法的名字。 例如,它们就像_​​add(),_edit(),_delete()等

因此,当你想从unit testing的angular度来testing它时,只需通过前缀和/或后缀一些常用单词(例如_addPhpunit)来调用私有方法,以便在调用__call()方法时(因为方法_addPhpunit()不存在)所有者类,您只需在__call()方法中添加必要的代码以除去前缀/后缀的单词(Phpunit),然后从那里调用推导出的私有方法。 这是神奇的方法的另一个很好的使用。

试试看。