수색…
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
의 값을 반환합니다. 그러나이 테스트에는 두 가지 방법으로 결함이 있습니다.
- 이 테스트의 범위를 벗어나는
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;
}
}
오류가 보이십니까? 개발자는 자동차의 색상에 "메탈릭"을 붙이기 위해 연결 연산자 대신 세미콜론을 사용했습니다. 결과적으로 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
의 값을 얻는 방법입니다.
- Car 객체를 나타내는 새로운 ReflectionObject를 생성합니다.
-
Car::$color
대한 ReflectionProperty 를 얻습니다 ( "this"는Car::$color
변수를 나타냅니다) - 우리는
Car::$color
접근 가능하게 만든다. - 우리는
Car::$color
의 값을 얻습니다.
Reflection을 사용하면 Car::getColor()
를 호출하지 않고도 Car::$color
의 값을 얻을 수 있거나 잘못된 테스트 결과를 초래할 수있는 다른 접근 함수를 얻을 수 있습니다. 이제 Car::setColor()
대한 단위 테스트는 안전하고 정확합니다.
클래스 또는 객체의 기능 감지
클래스의 기능 감지는 property_exists
및 method_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_exists
및 method_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);
}
}