訪問私有和受保護的成員變數

反射通常用作軟體測試的一部分,例如用於模擬物件的執行時建立/例項化。它對於在任何給定時間點檢查物件的狀態也很有用。下面是在單元測試中使用 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() 的單元測試是安全和準確的。