我发现有关“您测试私有方法的信息性” 的讨论。
我已经决定,在某些类中,我想拥有受保护的方法,但要对其进行测试。这些方法中的一些是静态的且简短的。因为大多数公共方法都使用它们,所以我稍后可能会安全地删除测试。但是,为了从TDD方法入手并避免调试,我真的想对其进行测试。
我想到了以下几点:
哪个是最佳做法?还有别的事吗?
看来,JUnit会自动将受保护的方法更改为公共方法,但我对此没有更深入的了解。PHP不允许通过反射进行此操作。
我发现有关“您测试私有方法的信息性” 的讨论。
我已经决定,在某些类中,我想拥有受保护的方法,但要对其进行测试。这些方法中的一些是静态的且简短的。因为大多数公共方法都使用它们,所以我稍后可能会安全地删除测试。但是,为了从TDD方法入手并避免调试,我真的想对其进行测试。
我想到了以下几点:
哪个是最佳做法?还有别的事吗?
看来,JUnit会自动将受保护的方法更改为公共方法,但我对此没有更深入的了解。PHP不允许通过反射进行此操作。
Answers:
如果您将PHP5(> = 5.3.2)与PHPUnit一起使用,则可以在运行测试之前通过使用反射将它们设置为公共来测试私有和受保护的方法:
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(...));
...
}
protected
方法也是公共api的一部分,因为任何第三方类都可以在没有任何魔术的情况下扩展和使用它。因此,我认为只有private
方法属于无法直接测试的方法类别。protected
并public
应直接进行测试。
您似乎已经知道了,但是我还是要重申一下。如果您需要测试受保护的方法,这是一个不好的信号。单元测试的目的是测试类的接口,而受保护的方法是实现的细节。也就是说,在某些情况下这是有道理的。如果使用继承,则可以将超类视为提供子类的接口。因此,在这里,您将必须测试受保护的方法(但决不能测试私有方法)。解决方案是创建一个用于测试目的的子类,并使用该子类公开方法。例如。:
class Foo {
protected function stuff() {
// secret stuff, you want to test
}
}
class SubFoo extends Foo {
public function exposedStuff() {
return $this->stuff();
}
}
请注意,您始终可以用组合替换继承。测试代码时,使用这种模式的代码通常要容易得多,因此您可能需要考虑使用该选项。
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);
}
}
您可以在测试中简单地通过以下方式调用此方法:
$returnVal = PHPUnitUtil::callMethod(
$this->object,
'_nameOfProtectedMethod',
array($arg1, $arg2)
);
我想对uckelman的answer中定义的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()来明确期望的内容,但这取决于您。
干杯!
我认为troelskn很近。我会这样做:
class ClassToTest
{
protected function testThisMethod()
{
// Implement stuff here
}
}
然后,实现如下所示:
class TestClassToTest extends ClassToTest
{
public function testThisMethod()
{
return parent::testThisMethod();
}
}
然后,您针对TestClassToTest运行测试。
通过解析代码,应该可以自动生成此类扩展类。如果PHPUnit已经提供了这样的机制,我不会感到惊讶(尽管我还没有检查过)。
您确实可以以通用方式使用__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替换为Example。
class ExampleTest extends PHPUnit_Framework_TestCase {
function testGetMessage() {
$fixture = new ExampleExposed();
self::assertEquals('hello', $fixture->getMessage());
}
}
我相信PHP 5.3允许您使用反射直接更改方法的可访问性,但是我认为您必须分别为每个方法进行更改。
call_user_method_array()
自PHP 4.1.0起不推荐使用该函数... call_user_func_array(array($this, $method), $args)
改为使用。请注意,如果您使用的是PHP 5.3.2+,则可以使用Reflection 获得对受保护/私有方法和属性的访问
Accessible
包,该包使用反射来允许测试访问私有/受保护的属性以及类和对象的方法。
__call()
仅在调用者无权访问该方法时被调用。由于该类及其子类可以访问受保护的方法,因此对它们的调用不会通过__call()
。您可以在新问题中发布在5.2.7中不起作用的代码吗?我在5.2中使用了以上内容,而仅在5.3.2中使用了反射。
我建议按照以下“ Henrik Paul”的解决方法/想法进行解决:)
您知道类的私有方法的名称。例如,它们就像_add(),_ edit(),_ delete()等。
因此,当您要从单元测试的角度对其进行测试时,只需通过在一些常见的前缀和/或后缀上调用私有方法即可词(例如_addPhpunit)后以便在调用__call()方法时(因为方法_addPhpunit()不会)所有者类的存在),您只需将必要的代码放在__call()方法中,以删除带有前缀/后缀的单词(Phpunit),然后从那里调用推导的私有方法。这是魔术方法的另一种很好的用法。
试试看。