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

时间:2016-08-31 19:05:26

标签: php unit-testing phpunit

我环顾四周,找到了一个适用于普通物体的解决方案,但它似乎不适用于嘲笑。

以下测试失败并显示消息: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 更新了代码以回复answer by Katie

2 个答案:

答案 0 :(得分:3)

您正在尝试在模拟对象上调用Reflection方法,而是可以在抽象类本身上调用它:

所以改变:

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

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

这适用于类中不抽象的任何内容,例如您的属性或完全实现的其他方法。

OP更新后的附加说明

此修复程序仍适用于getReflectionProperty中的第一行。但是,如果您无法访问类名,那么这就是一个问题。

答案 1 :(得分:2)

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

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

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

但是,有时需要为抽象类编写测试用例。在我看来,最好的方法是将抽象类扩展到包含测试用例的文件的底部,为其所有抽象方法提供简单的实现,并将此类用作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连接等);
  • 合作者的代码具有永久的副作用;这通常与之前的原因相结合。