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

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

我想到了以下几点:

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

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

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


当前回答

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();
  }
}

注意,您总是可以用组合替换继承。在测试代码时,处理使用这种模式的代码通常要容易得多,因此您可能需要考虑该选项。

其他回答

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();
  }
}

注意,您总是可以用组合替换继承。在测试代码时,处理使用这种模式的代码通常要容易得多,因此您可能需要考虑该选项。

你确实可以以通用的方式使用__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允许您使用反射来直接更改方法的可访问性,但我假设您必须对每个方法单独这样做。

我在这里郑重声明:

我使用过__call黑客,成功程度不一。 我想到的另一种选择是使用访问者模式:

1:生成一个stdClass或自定义类(以加强类型)

2:用所需的方法和参数来启动它

3:确保你的SUT有一个acceptVisitor方法,它将使用访问类中指定的参数执行方法

4:将其注入到您希望测试的类中

5: SUT将操作结果注入访问者

6:将测试条件应用到访问者的结果属性

您可以像下面的代码一样使用闭包

<?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!

我想对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()来显式显示期望的内容,但这取决于您。