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

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

我想到了以下几点:

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

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

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


当前回答

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

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']);

其他回答

如果你在PHPUnit中使用PHP5(>= 5.3.2),你可以在运行测试之前使用反射将私有方法和受保护方法设置为公共:

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

我想troelskn就在附近。我会这样做:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}

然后,实现如下内容:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}

然后根据TestClassToTest运行测试。

应该可以通过解析代码自动生成这样的扩展类。如果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 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!

我在这里郑重声明:

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

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

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

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

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

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

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