我发现了关于你是否测试私有方法的讨论。
我已经决定,在某些类中,我希望有受保护的方法,但要测试它们。
其中一些方法是静态的和简短的。因为大多数公共方法都使用了这些测试,所以我以后可能会安全地删除这些测试。但是为了从TDD方法开始并避免调试,我真的很想测试它们。
我想到了以下几点:
在回答中建议的方法对象似乎是多余的。
从公共方法开始,当代码覆盖由更高级别的测试提供时,将它们变为受保护的,并删除测试。
继承一个具有可测试接口的类,该接口使受保护的方法公开
哪种是最佳实践?还有别的事吗?
看起来,JUnit会自动将受保护的方法更改为公共方法,但我并没有深入了解它。PHP不允许通过反射进行此操作。
你确实可以以通用的方式使用__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());
}
}
我相信PHP 5.3允许您使用反射来直接更改方法的可访问性,但我假设您必须对每个方法单独这样做。
您可以像下面的代码一样使用闭包
<?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!
为了单元测试的目的,我做了一个类来调用简单的私有方法(静态和非静态):
class MethodInvoker
{
public function invoke($object, string $methodName, array $args=[]) {
$privateMethod = $this->getMethod(get_class($object), $methodName);
return $privateMethod->invokeArgs($object, $args);
}
private function getMethod(string $className, string $methodName) {
$class = new \ReflectionClass($className);
$method = $class->getMethod($methodName);
$method->setAccessible(true);
return $method;
}
}
用法示例:
class TestClass {
private function privateMethod(string $txt) {
print_r('invoked privateMethod: ' . $txt);
}
}
(new MethodInvoker)->invoke(new TestClass, 'privateMethod', ['argument_1']);
的选择。下面的代码是作为示例提供的。
它的实施可以更广泛。
它的实现将帮助您测试私有方法并替换私有属性。
<?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