访问私有和受保护的成员变量

反射通常用作软件测试的一部分,例如用于模拟对象的运行时创建/实例化。它对于在任何给定时间点检查对象的状态也很有用。下面是在单元测试中使用 Reflection 来验证受保护的类成员是否包含期望值的示例。

下面是一个非常基本的汽车类。它有一个受保护的成员变量,它将包含表示汽车颜色的值。因为成员变量是受保护的,所以我们无法直接访问它,并且必须使用 getter 和 setter 方法分别检索和设置其值。

class Car
{
    protected $color
    
    public function setColor($color)
    {
        $this->color = $color;
    }
    
    public function getColor($color)
    {
        return $this->color;
    }
}

为了测试这一点,许多开发人员将创建一个 Car 对象,使用 Car::setColor() 设置汽车的颜色,使用 Car::getColor() 检索颜色,并将该值与他们设置的颜色进行比较:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';

    $car = new \Car();
    $car->setColor($color);
    $getColor = $car->getColor();
        
    $this->assertEquals($color, $reflectionColor);
}

从表面上看,这似乎没问题。毕竟,所有 Car::getColor() 都会返回受保护成员变量 Car::$color 的值。但是这个测试有两个方面存在缺陷:

  1. 它练习 Car::getColor(),这超出了本次测试的范围
  2. 它取决于 Car::getColor(),它本身可能有一个错误,可以使测试产生假阳性或阴性

让我们看一下为什么我们不应该在单元测试中使用 Car::getColor(),而应该使用 Reflection。假设开发人员被分配了一项任务,即为每种车型添加金属。所以他们试图修改 Car::getColor() 以将 Metallic 添加到汽车的颜色中:

class Car
{
    protected $color
    
    public function setColor($color)
    {
        $this->color = $color;
    }
    
    public function getColor($color)
    {
        return "Metallic "; $this->color;
    }
}

你看到错误吗?开发人员使用分号而不是连接运算符来尝试将 Metallic 添加到汽车的颜色中。因此,每当调用 Car::getColor() 时,无论汽车的实际颜色是什么,都将返回金属。因此,我们的 Car::setColor() 单元测试将失败,即使 Car::setColor() 工作完全正常并且不受此变化的影响

那么我们如何验证 Car::$color 是否包含我们通过 Car::setColor() 设置的值?我们可以使用 Refelection 直接检查受保护的成员变量。那么我们怎么办?我们可以使用 Refelection 使受保护的成员变量可以访问我们的代码,以便它可以检索该值。

让我们先看看代码,然后将其分解:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';

    $car = new \Car();
    $car->setColor($color);
    
    $reflectionOfCar = new \ReflectionObject($car);
    $protectedColor = $reflectionOfForm->getProperty('color');
    $protectedColor->setAccessible(true);
    $reflectionColor = $protectedColor->getValue($car);
    
    $this->assertEquals($color, $reflectionColor);
}

以下是我们如何使用 Reflection 在上面的代码中获取 Car::$color 的值:

  1. 我们创建一个表示 Car 对象的新 ReflectionObject
  2. 我们得到了 Car::$colorReflectionProperty (这个代表Car::$color 变量)
  3. 我们让 Car::$color 可以访问
  4. 我们得到了 Car::$color 的价值

正如你可以通过使用 Reflection 看到的那样,我们可以获得 Car::$color 的值,而无需调用 Car::getColor() 或任何其他可能导致无效测试结果的访问器函数。现在我们对 Car::setColor() 的单元测试是安全和准确的。