수색…


private 및 protected 멤버 변수 액세스

리플렉션은 종종 모의 객체의 런타임 생성 / 인스턴스 생성과 같은 소프트웨어 테스팅의 일부로 사용됩니다. 또한 특정 시점에서 객체의 상태를 검사하는 데 유용합니다. 다음은 단위 테스트에서 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::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;
    }
}

오류가 보이십니까? 개발자는 자동차의 색상에 "메탈릭"을 붙이기 위해 연결 연산자 대신 세미콜론을 사용했습니다. 결과적으로 Car::getColor() 가 호출 될 때마다 Car::getColor() 의 실제 색상과 상관없이 "Metallic"이 반환됩니다. 결과적으로 Car::setColor() 단위 테스트는 Car::setColor() 가 완벽하게 작동하고이 변경의 영향을받지 않더라도 실패합니다.

Car::setColor() 를 통해 설정 한 값이 Car::$color 포함되어 있는지 확인하려면 어떻게해야합니까? 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::$color 대한 ReflectionProperty 를 얻습니다 ( "this"는 Car::$color 변수를 나타냅니다)
  3. 우리는 Car::$color 접근 가능하게 만든다.
  4. 우리는 Car::$color 의 값을 얻습니다.

Reflection을 사용하면 Car::getColor() 를 호출하지 않고도 Car::$color 의 값을 얻을 수 있거나 잘못된 테스트 결과를 초래할 수있는 다른 접근 함수를 얻을 수 있습니다. 이제 Car::setColor() 대한 단위 테스트는 안전하고 정확합니다.

클래스 또는 객체의 기능 감지

클래스의 기능 감지는 property_existsmethod_exists 기능을 사용하여 부분적으로 수행 할 수 있습니다.

class MyClass {
    public $public_field;
    protected $protected_field;
    private $private_field;
    static $static_field;
    const CONSTANT = 0;
    public function public_function() {}
    protected function protected_function() {}
    private function private_function() {}
    static function static_function() {}
}

// check properties
$check = property_exists('MyClass', 'public_field');    // true
$check = property_exists('MyClass', 'protected_field'); // true
$check = property_exists('MyClass', 'private_field');   // true, as of PHP 5.3.0
$check = property_exists('MyClass', 'static_field');    // true
$check = property_exists('MyClass', 'other_field');     // false

// check methods
$check = method_exists('MyClass', 'public_function');    // true
$check = method_exists('MyClass', 'protected_function');    // true
$check = method_exists('MyClass', 'private_function');    // true
$check = method_exists('MyClass', 'static_function');    // true

// however...
$check = property_exists('MyClass', 'CONSTANT');  // false
$check = property_exists($object, 'CONSTANT');    // false

ReflectionClass 사용하면 상수도 감지 할 수 있습니다.

$r = new ReflectionClass('MyClass');
$check = $r->hasProperty('public_field');  // true
$check = $r->hasMethod('public_function'); // true
$check = $r->hasConstant('CONSTANT');      // true
// also works for protected, private and/or static members.

참고 : property_existsmethod_exists 의 경우 클래스 이름 대신 관심 클래스의 개체를 제공 할 수도 있습니다. 반사를 사용하여 ReflectionObject 클래스 대신 사용해야합니다 ReflectionClass .

개인 / 보호 된 메소드 테스트

때로는 개인용 및 보호 된 메서드는 물론 공용 메서드도 테스트하는 것이 유용합니다.

class Car
{
    /**
     * @param mixed $argument
     *
     * @return mixed
     */
    protected function drive($argument)
    {
        return $argument;
    }

    /**
     * @return bool
     */
    private static function stop()
    {
        return true;
    }
}

리플렉션을 사용하여 드라이브 방법을 테스트하는 가장 쉬운 방법

class DriveTest
{
    /**
     * @test
     */
    public function testDrive()
    {
        // prepare
        $argument = 1;
        $expected = $argument;
        $car = new \Car();

        $reflection = new ReflectionClass(\Car::class);
        $method = $reflection->getMethod('drive');
        $method->setAccessible(true);

        // invoke logic
        $result = $method->invokeArgs($car, [$argument]);

        // test
        $this->assertEquals($expected, $result);
    }
}

메서드가 정적 인 경우 클래스 인스턴스 대신 null을 전달합니다.

class StopTest
{
    /**
     * @test
     */
    public function testStop()
    {
        // prepare
        $expected = true;

        $reflection = new ReflectionClass(\Car::class);
        $method = $reflection->getMethod('stop');
        $method->setAccessible(true);

        // invoke logic
        $result = $method->invoke(null);

        // test
        $this->assertEquals($expected, $result);
    }
}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow