使用PHPUnit测试受保护方法的最佳实践

使用PHPUnit测试受保护方法的最佳实践,php,unit-testing,phpunit,Php,Unit Testing,Phpunit,我发现讨论内容丰富 我已经决定,在某些类中,我希望有受保护的方法,但要测试它们。 其中一些方法是静态的和简短的。因为大多数公共方法都使用它们,所以我以后可能能够安全地删除这些测试。但是为了从TDD方法开始并避免调试,我真的很想测试它们 我想到了以下几点: 正如年所建议的那样,这似乎是矫枉过正 从公共方法开始,当代码覆盖率由更高级别的测试提供时,将它们转换为受保护的,并删除测试 继承具有可测试接口的类,使受保护的方法公开 哪一种是最佳实践?还有别的吗 JUnit似乎会自动将受保护的方法更改为公

我发现讨论内容丰富

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

我想到了以下几点:

  • 正如年所建议的那样,这似乎是矫枉过正
  • 从公共方法开始,当代码覆盖率由更高级别的测试提供时,将它们转换为受保护的,并删除测试
  • 继承具有可测试接口的类,使受保护的方法公开
哪一种是最佳实践?还有别的吗


JUnit似乎会自动将受保护的方法更改为公共方法,但我并没有深入研究它。PHP不允许这一点。

您似乎已经意识到了,但我还是要重申一下;如果您需要测试受保护的方法,这是一个不好的迹象。单元测试的目的是测试类的接口,受保护的方法是实现细节。也就是说,在某些情况下,这是有道理的。如果使用继承,则可以将超类视为为为子类提供接口。因此,在这里,您必须测试受保护的方法(但绝不是私有方法)。解决方案是创建一个子类用于测试,并使用它公开方法。例如:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

请注意,您始终可以用组合替换继承。在测试代码时,处理使用这种模式的代码通常要容易得多,所以您可能想考虑这个选项。

< P>我认为Toelelskn很接近。我会这样做:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}
然后,实现如下内容:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}
然后针对TestClassToTest运行测试


应该可以通过解析代码自动生成这样的扩展类。如果PHPUnit已经提供了这样一种机制,我也不会感到惊讶(尽管我没有检查)。

我建议“Henrik Paul”的解决方案/想法如下:)

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

因此,当您想从单元测试的角度对其进行测试时,只需通过前缀和/或后缀一些常用词(例如_addPhpunit)来调用私有方法,这样当调用所有者类的u call()方法时(因为方法_addPhpunit()不存在),您只需在u call()方法中放入必要的代码来删除前缀/后缀词(Phpunit)然后从那里调用导出的私有方法。这是魔法方法的另一个很好的用法

试试看。

您确实可以以通用方式使用u 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());
    }
}
我相信PHP5.3允许您使用反射来直接更改方法的可访问性,但我假设您必须对每个方法单独进行更改。

如果您在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(...));
  ...
}

我要在这里把我的帽子扔进拳击场:

我使用了uu call hack,成功程度参差不齐。 我提出的另一种选择是使用访问者模式:

1:生成stdClass或自定义类(以强制类型)

2:使用所需的方法和参数初始化

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

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

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


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

我想对中定义的getMethod()提出一个小小的修改

此版本通过删除硬编码值并稍微简化使用来更改getMethod()。我建议将其添加到PHPUnitUtil类中,如下面的示例所示,或者添加到PHPUnit_Framework_TestCase扩展类中(或者,我想,全局添加到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(),以明确预期的内容,但这取决于您。

有正确的方法。更简单的方法是直接调用该方法并返回答案:

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

嘿……我似乎在说,使用你的第三个选择:)是的,这正是我的第三个选择。我很确定,PHPUnit不提供这样的机制。这不起作用,你不能用同名的公共函数覆盖受保护的函数。我可能错了,但我认为这种方法不起作用。PHPUnit(据我所知)要求您的测试类扩展另一个提供实际测试功能的类。除非有办法解决这个问题,否则我不确定我能看到这个答案是如何使用的。仅供参考,这只适用于受保护的方法,不适用于私有方法。您可以直接将stuff()实现为public并返回parent::stuff()。请看我的回答。看来我今天读东西太快了。你说得对;将受保护的方法更改为公共方法是有效的。我不同意这是一个坏迹象。让我们区分TDD和单元测试。单元测试应该测试imo中的私有方法,因为它们是单元,并且会从中受益