使用Reflection在单元测试中设置属性

时间:2017-05-30 10:49:11

标签: php unit-testing phpunit

以下是我的情景:

我创建了一个抽象类来减少代码重复,并强制执行一些方法来扩展这个类的具体类。

abstract class BaseClass
{
    protected $arrayField;

    ...

    public function getModifiedArrayField($constraints)
    {
        // do things to $arrayField and return a modified
        // version of the arrayField. Depends only on the
        // $constraints and some `array_*` functions
        return $modifiedArray;
    }

    // ... some other methods for reducing code duplication

    // ... abstract methods that needs to be implemented

}

我认为在抽象类本身中测试具体方法是个好主意。

$arrayField将由具体类中的抽象方法填充。

由于我想测试修改该属性的方法的功能(但不会改变),我写了一个单元测试如下。


class BaseClassTest extents PHPUnit_Framework_TestCase

    private $sut;

    public function setUp()
    {
        $mockObj = $this->getMockFromAbstractClass(BaseClass::class);
        $ref = new ReflectionClass($mockObj);
        $ref_prop = $ref->getProperty('arrayField');
        $ref_prop->setAccessible(true);
        $ref_prop->setValue($mockObj, [an_array]);
        $this->sut = $mockObj;
    }

    // .. some test methods that tests methods of abstract class

    public function testGetModifiedArrayFieldReturnsExpectedArray()
    {
        $expected = [array_i_expect];
        $actual = $this->sut->getModifiedArrayField([constraints_i_provide]);

        $this->assertEquals($expected, $actual);
    }

现在,我读到,为了测试,使用Reflection来改变可见性并不是一个好习惯。

所以我有两个问题:

  1. 测试社区是否认为这种方法只是一种“轻微进攻”而不是一项重大犯罪?
  2. 我应该以不同的方式设计这个类,以便我可以摆脱反射吗?如果是,您建议使用任何方法吗?
  3. 更新:

    <{3}} answer让我思考,在与我的朋友和互联网上的一些资源进行一些聊天之后,我选择通过在我需要测试的函数中引入一个额外的参数来解决这个问题。

    我将继续尝试概括函数并将其移动到ArrayHelper类,这使得1)更容易测试它2)如果需要,允许代码的其他部分使用该方法。

1 个答案:

答案 0 :(得分:1)

使用反射的问题在于您正在暴露类内部。单元测试有助于证明您的代码正常工作,但也允许您重构事物并验证您没有更改任何当前功能。

因此,使用反射来更改内部属性的可见性意味着您可以稍后删除/更改该属性。例如,您可能会发现您可以即时计算它或类似的东西。

难以测试一个类是代码气味。

为什么$arrayField只能通过抽象方法设置?如果我在调用getModifiedArrayField()之前没有在该类的实现中调用该方法会发生什么?

根据帖子中的名称和描述,很难说出您对此类的意图以及方法。但是,我认为您可能需要考虑更多您希望班级做的事情。并更改您的类以要求在构造函数上提供$arrayField或提供一个简单的公共方法来设置此类中的值。