如何为抽象类设置模拟的受保护属性


How can I set protected properties of mocks for abstract classes?

我已经环顾四周,我找到了一个解决方案,适用于正常对象,但它似乎不适合模拟。

下面的测试失败,消息为:Unable to set property someProperty of object type Mock_ClassToTest_40ea0b83: Property someProperty does not exist .

class sampleTestClass extends PHPUnit_Framework_TestCase
{
    function test() {
        $object = $this->getMockForAbstractClass(ClassToTest::class, [], '', false);
        $this->setProtectedProperty($object, 'someProperty', 'value');
    }
    private function getReflectionProperty($object, $property) {
        $reflection         = new ReflectionClass($object);
        $reflectionProperty = $reflection->getProperty($property);
        $reflectionProperty->setAccessible(true);
        return $reflectionProperty;
    }
    /**
     * This method modifies the protected properties of any object.
     * @param object $object   The object to modify.
     * @param string $property The name of the property to modify.
     * @param mixed  $value    The value to set.
     * @throws TestingException
     */
    function setProtectedProperty(&$object, $property, $value) {
        try {
            $reflectionProperty = $this->getReflectionProperty($object, $property);
            $reflectionProperty->setValue($object, $value);
        }
        catch ( Exception $e ) {
            throw new TestingException("Unable to set property {$property} of object type " . get_class($object) .
                                       ': ' . $e->getMessage(), 0, $e);
        }
    }
}
abstract class ClassToTest
{
    private $someProperty;
    abstract function someFunc();
}
class TestingException extends Exception
{
}
编辑:2016年8月31日美国东部时间下午4:32

您正在尝试在模拟对象上调用反射方法,相反,您可以在抽象类本身上调用它:

所以改变:

$reflection = new ReflectionClass(get_class($object));

$reflection = new ReflectionClass(ClassToTest::class);

这将适用于类中任何非抽象的东西,例如您的属性,或另一个完全实现的方法。

OP更新后的附加注释

这个修复仍然会对你的第一行getReflectionProperty起作用。但是,如果您无法访问类名,那么这就是一个问题。

在测试中使用反射来访问受保护的私有属性和类的方法似乎是一个非常聪明的方法,但它会导致测试难以阅读和理解。

另一方面,只测试类的公共接口。测试(甚至关心)被测试类的受保护属性和私有属性以及方法表明测试是在代码之后编写的。这样的测试是脆弱的;被测试类的实现中的任何变化都会破坏测试,即使它没有破坏类的功能。

通常不需要测试抽象类。大多数情况下,其子类的测试也涵盖了抽象类的相关代码。如果它们没有覆盖它的某些部分,那么要么代码不需要在那里,要么测试用例没有覆盖所有的角落用例。

然而,有时需要为抽象类编写测试用例。在我看来,最好的方法是扩展包含测试用例的文件底部的抽象类,为其所有抽象方法提供简单的实现,并将该类用作SUT。

下面的内容:

class sampleTestClass extends PHPUnit_Framework_TestCase
{
    public function testSomething()
    {
        $object = new ConcreteImplementation();
        $result = $object->method1();
        self::assertTrue($result);
    }
}
class ConcreteImplementation extends AbstractClassToTest
{
    public function someFunc()
    {
        // provide the minimum implementation that makes it work
    }
}

您正在测试您发布的代码中的模拟。这些模拟不是用来测试的。它们的目的是模拟不适合在测试中实例化的SUT合作者的行为。

合作者类在测试中被模拟的原因包括但不限于:

  • 困难创造;例如,当模拟类的构造函数需要许多参数或其他对象时;
  • 合作者是一个抽象类或接口;在编写测试和被测试类时,实际的实现甚至可能不存在;
  • 合作者的代码需要大量时间来完成或需要额外的资源(磁盘空间,数据库连接,Internet连接等);
  • 合作者的代码有永久性的副作用;这通常与前一个原因相结合。