我发现了关于你是否测试私有方法的讨论。
我已经决定,在某些类中,我希望有受保护的方法,但要测试它们。
其中一些方法是静态的和简短的。因为大多数公共方法都使用了这些测试,所以我以后可能会安全地删除这些测试。但是为了从TDD方法开始并避免调试,我真的很想测试它们。
我想到了以下几点:
在回答中建议的方法对象似乎是多余的。
从公共方法开始,当代码覆盖由更高级别的测试提供时,将它们变为受保护的,并删除测试。
继承一个具有可测试接口的类,该接口使受保护的方法公开
哪种是最佳实践?还有别的事吗?
看起来,JUnit会自动将受保护的方法更改为公共方法,但我并没有深入了解它。PHP不允许通过反射进行此操作。
的选择。下面的代码是作为示例提供的。
它的实施可以更广泛。
它的实现将帮助您测试私有方法并替换私有属性。
<?php
class Helper{
public static function sandbox(\Closure $call,$target,?string $slaveClass=null,...$args)
{
$slaveClass=!empty($slaveClass)?$slaveClass:(is_string($target)?$target:get_class($target));
$target=!is_string($target)?$target:null;
$call=$call->bindTo($target,$slaveClass);
return $call(...$args);
}
}
class A{
private $prop='bay';
public function get()
{
return $this->prop;
}
}
class B extends A{}
$b=new B;
$priv_prop=Helper::sandbox(function(...$args){
return $this->prop;
},$b,A::class);
var_dump($priv_prop);// bay
Helper::sandbox(function(...$args){
$this->prop=$args[0];
},$b,A::class,'hello');
var_dump($b->get());// hello
我想对uckelman的答案中定义的getMethod()提出一个轻微的变化。
这个版本更改了getMethod(),删除了硬编码的值,并略微简化了用法。我建议将它添加到PHPUnitUtil类中,如下面的例子所示,或者添加到PHPUnit_Framework_TestCase-extending类中(或者,我认为,全局地添加到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()来显式显示期望的内容,但这取决于您。
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)
);
您可以像下面的代码一样使用闭包
<?php
class A
{
private string $value = 'Kolobol';
private string $otherPrivateValue = 'I\'m very private, like a some kind of password!';
public function setValue(string $value): void
{
$this->value = $value;
}
private function getValue(): string
{
return $this->value . ': ' . $this->getVeryPrivate();
}
private function getVeryPrivate()
{
return $this->otherPrivateValue;
}
}
$getPrivateProperty = function &(string $propName) {
return $this->$propName;
};
$getPrivateMethod = function (string $methodName) {
return Closure::fromCallable([$this, $methodName]);
};
$objA = new A;
$getPrivateProperty = Closure::bind($getPrivateProperty, $objA, $objA);
$getPrivateMethod = Closure::bind($getPrivateMethod, $objA, $objA);
$privateByLink = &$getPrivateProperty('value');
$privateMethod = $getPrivateMethod('getValue');
echo $privateByLink, PHP_EOL; // Kolobok
$objA->setValue('Zmey-Gorynich');
echo $privateByLink, PHP_EOL; // Zmey-Gorynich
$privateByLink = 'Alyonushka';
echo $privateMethod(); // Alyonushka: I'm very private, like a some kind of password!
You seem to be aware already, but I'll just restate it anyway; It's a bad sign, if you need to test protected methods. The aim of a unit test, is to test the interface of a class, and protected methods are implementation details. That said, there are cases where it makes sense. If you use inheritance, you can see a superclass as providing an interface for the subclass. So here, you would have to test the protected method (But never a private one). The solution to this, is to create a subclass for testing purpose, and use this to expose the methods. Eg.:
class Foo {
protected function stuff() {
// secret stuff, you want to test
}
}
class SubFoo extends Foo {
public function exposedStuff() {
return $this->stuff();
}
}
注意,您总是可以用组合替换继承。在测试代码时,处理使用这种模式的代码通常要容易得多,因此您可能需要考虑该选项。