サーチ…
プライベートおよび保護されたメンバ変数へのアクセス
リフレクションは、模擬オブジェクトのランタイム作成/インスタンス化など、ソフトウェアテストの一部としてよく使用されます。また、任意の時点でオブジェクトの状態を検査するのにも最適です。ユニットテストでリフレクションを使用して、保護されたクラスメンバーに期待値が含まれていることを確認する例を次に示します。
以下は、車の非常に基本的なクラスです。それは車の色を表す値を含む保護されたメンバ変数を持っています。メンバー変数は保護されているため、直接アクセスすることはできず、getterおよびsetterメソッドを使用してそれぞれの値を取得および設定する必要があります。
class Car
{
protected $color
public function setColor($color)
{
$this->color = $color;
}
public function getColor($color)
{
return $this->color;
}
}
これをテストするには、Carオブジェクトを作成し、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
の値を返します。しかし、このテストには2通りの方法があります。
- このテストの対象外の
Car::getColor()
を実行します。 - それは
Car::getColor()
に依存します。これはテストに偽陽性または陰性を持たせるバグを持っている可能性があります
ユニットテストでCar::getColor()
使わない理由を見てみましょう。代わりにReflectionを使うべきです。開発者に、すべてのカーカラーに「メタリック」を追加するタスクが割り当てられているとします。そこで、 Car::getColor()
を修正して、車の色に「メタリック」をCar::getColor()
ます。
class Car
{
protected $color
public function setColor($color)
{
$this->color = $color;
}
public function getColor($color)
{
return "Metallic "; $this->color;
}
}
エラーが表示されますか?開発者は、連結演算子の代わりにセミコロンを使用して、車の色に「メタリック」を付加しました。結果として、 Car::getColor()
が呼び出されるたびに、自動車の実際の色が何であるかに関係なく、「メタリック」が返されます。その結果、 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を取得しCar::$color
(これは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
を使用すると、 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);
}
}