访问私有和受保护的成员变量
反射通常用作软件测试的一部分,例如用于模拟对象的运行时创建/实例化。它对于在任何给定时间点检查对象的状态也很有用。下面是在单元测试中使用 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
的值。但是这个测试有两个方面存在缺陷:
- 它练习
Car::getColor()
,这超出了本次测试的范围 - 它取决于
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
的值:
- 我们创建一个表示 Car 对象的新 ReflectionObject
- 我们得到了
Car::$color
的 ReflectionProperty (这个代表Car::$color
变量) - 我们让
Car::$color
可以访问 - 我们得到了
Car::$color
的价值
正如你可以通过使用 Reflection 看到的那样,我们可以获得 Car::$color
的值,而无需调用 Car::getColor()
或任何其他可能导致无效测试结果的访问器函数。现在我们对 Car::setColor()
的单元测试是安全和准确的。