使用PHPUnit测试受保护方法的最佳实践
我发现讨论内容丰富 我已经决定,在某些类中,我希望有受保护的方法,但要测试它们。 其中一些方法是静态的和简短的。因为大多数公共方法都使用它们,所以我以后可能能够安全地删除这些测试。但是为了从TDD方法开始并避免调试,我真的很想测试它们 我想到了以下几点:使用PHPUnit测试受保护方法的最佳实践,php,unit-testing,phpunit,Php,Unit Testing,Phpunit,我发现讨论内容丰富 我已经决定,在某些类中,我希望有受保护的方法,但要测试它们。 其中一些方法是静态的和简短的。因为大多数公共方法都使用它们,所以我以后可能能够安全地删除这些测试。但是为了从TDD方法开始并避免调试,我真的很想测试它们 我想到了以下几点: 正如年所建议的那样,这似乎是矫枉过正 从公共方法开始,当代码覆盖率由更高级别的测试提供时,将它们转换为受保护的,并删除测试 继承具有可测试接口的类,使受保护的方法公开 哪一种是最佳实践?还有别的吗 JUnit似乎会自动将受保护的方法更改为公
- 正如年所建议的那样,这似乎是矫枉过正
- 从公共方法开始,当代码覆盖率由更高级别的测试提供时,将它们转换为受保护的,并删除测试
- 继承具有可测试接口的类,使受保护的方法公开
JUnit似乎会自动将受保护的方法更改为公共方法,但我并没有深入研究它。PHP不允许这一点。您似乎已经意识到了,但我还是要重申一下;如果您需要测试受保护的方法,这是一个不好的迹象。单元测试的目的是测试类的接口,受保护的方法是实现细节。也就是说,在某些情况下,这是有道理的。如果使用继承,则可以将超类视为为为子类提供接口。因此,在这里,您必须测试受保护的方法(但绝不是私有方法)。解决方案是创建一个子类用于测试,并使用它公开方法。例如:
class Foo {
protected function stuff() {
// secret stuff, you want to test
}
}
class SubFoo extends Foo {
public function exposedStuff() {
return $this->stuff();
}
}
请注意,您始终可以用组合替换继承。在测试代码时,处理使用这种模式的代码通常要容易得多,所以您可能想考虑这个选项。 < P>我认为Toelelskn很接近。我会这样做:
class ClassToTest
{
protected function testThisMethod()
{
// Implement stuff here
}
}
然后,实现如下内容:
class TestClassToTest extends ClassToTest
{
public function testThisMethod()
{
return parent::testThisMethod();
}
}
然后针对TestClassToTest运行测试
应该可以通过解析代码自动生成这样的扩展类。如果PHPUnit已经提供了这样一种机制,我也不会感到惊讶(尽管我没有检查)。我建议“Henrik Paul”的解决方案/想法如下:) 您知道类的私有方法的名称。例如,它们像_add(),_edit(),_delete()等等 因此,当您想从单元测试的角度对其进行测试时,只需通过前缀和/或后缀一些常用词(例如_addPhpunit)来调用私有方法,这样当调用所有者类的u call()方法时(因为方法_addPhpunit()不存在),您只需在u call()方法中放入必要的代码来删除前缀/后缀词(Phpunit)然后从那里调用导出的私有方法。这是魔法方法的另一个很好的用法 试试看。您确实可以以通用方式使用u call()来访问受保护的方法
class Example {
protected function getMessage() {
return 'hello';
}
}
在ExampleTest.php中创建一个子类:
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()方法不会以任何方式引用该类,因此您可以使用要测试的受保护方法为每个类复制上述内容,并仅更改类声明。您可以将此函数放置在公共基类中,但我没有尝试过
现在,测试用例本身的不同之处仅在于构建要测试的对象的位置,例如在ExampleExposed中交换
class ExampleTest extends PHPUnit_Framework_TestCase {
function testGetMessage() {
$fixture = new ExampleExposed();
self::assertEquals('hello', $fixture->getMessage());
}
}
我相信PHP5.3允许您使用反射来直接更改方法的可访问性,但我假设您必须对每个方法单独进行更改。如果您在PHPUnit中使用PHP5(>=5.3.2),您可以在运行测试之前使用反射将私有和受保护的方法设置为公共,从而测试它们:
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(...));
...
}
我要在这里把我的帽子扔进拳击场: 我使用了uu call hack,成功程度参差不齐。 我提出的另一种选择是使用访问者模式: 1:生成stdClass或自定义类(以强制类型) 2:使用所需的方法和参数初始化 3:确保SUT具有acceptVisitor方法,该方法将使用访问类中指定的参数执行该方法 4:将其注入到您希望测试的类中 5:SUT将操作结果注入访问者
6:将您的测试条件应用于访问者的结果属性我想对中定义的getMethod()提出一个小小的修改 此版本通过删除硬编码值并稍微简化使用来更改getMethod()。我建议将其添加到PHPUnitUtil类中,如下面的示例所示,或者添加到PHPUnit_Framework_TestCase扩展类中(或者,我想,全局添加到PHPUnitUtil文件中) 由于MyClass正在以任何方式实例化,ReflectionClass可以接受字符串或对象
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
}
我还创建了一个别名函数getProtectedMethod(),以明确预期的内容,但这取决于您。有正确的方法。更简单的方法是直接调用该方法并返回答案:
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);
}
}
您可以通过以下方式在测试中简单地调用它:
$returnVal = PHPUnitUtil::callMethod(
$this->object,
'_nameOfProtectedMethod',
array($arg1, $arg2)
);
嘿……我似乎在说,使用你的第三个选择:)是的,这正是我的第三个选择。我很确定,PHPUnit不提供这样的机制。这不起作用,你不能用同名的公共函数覆盖受保护的函数。我可能错了,但我认为这种方法不起作用。PHPUnit(据我所知)要求您的测试类扩展另一个提供实际测试功能的类。除非有办法解决这个问题,否则我不确定我能看到这个答案是如何使用的。仅供参考,这只适用于受保护的方法,不适用于私有方法。您可以直接将stuff()实现为public并返回parent::stuff()。请看我的回答。看来我今天读东西太快了。你说得对;将受保护的方法更改为公共方法是有效的。我不同意这是一个坏迹象。让我们区分TDD和单元测试。单元测试应该测试imo中的私有方法,因为它们是单元,并且会从中受益