我发现了关于你是否测试私有方法的讨论。

我已经决定,在某些类中,我希望有受保护的方法,但要测试它们。 其中一些方法是静态的和简短的。因为大多数公共方法都使用了这些测试,所以我以后可能会安全地删除这些测试。但是为了从TDD方法开始并避免调试,我真的很想测试它们。

我想到了以下几点:

在回答中建议的方法对象似乎是多余的。 从公共方法开始,当代码覆盖由更高级别的测试提供时,将它们变为受保护的,并删除测试。 继承一个具有可测试接口的类,该接口使受保护的方法公开

哪种是最佳实践?还有别的事吗?

看起来,JUnit会自动将受保护的方法更改为公共方法,但我并没有深入了解它。PHP不允许通过反射进行此操作。


当前回答

我建议以下“亨里克·保罗”的解决方案/想法的解决方案:)

您知道类的私有方法的名称。例如,它们像_add(), _edit(), _delete()等。

因此,当你想从单元测试的角度测试它时,只需通过添加前缀和/或后缀一些常见的单词(例如_addPhpunit)来调用私有方法,这样当所有者类的__call()方法被调用时(因为方法_addPhpunit()不存在),你只需在__call()方法中放入必要的代码来删除带前缀/后缀的单词/s (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。

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!

我建议以下“亨里克·保罗”的解决方案/想法的解决方案:)

您知道类的私有方法的名称。例如,它们像_add(), _edit(), _delete()等。

因此,当你想从单元测试的角度测试它时,只需通过添加前缀和/或后缀一些常见的单词(例如_addPhpunit)来调用私有方法,这样当所有者类的__call()方法被调用时(因为方法_addPhpunit()不存在),你只需在__call()方法中放入必要的代码来删除带前缀/后缀的单词/s (Phpunit),然后从那里调用推断出的私有方法。这是另一个很好的使用魔术的方法。

试试吧。

为了单元测试的目的,我做了一个类来调用简单的私有方法(静态和非静态):

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