使用例として、次のクラスの説明を使用します。
class MyClass { protected function showWord($word) { /* */ } protected function getTemperature() { /* */ } public getWord($temparature) { $temperature = (int)$temparature; if ($temperature < 15) { return 'cold'; } if ($temperature > 25) { return 'hot'; } return 'warm'; } public function process() { $temperature = $this->getTemperature(); $word = $this->getWord($temperature); $this->showWord($word); } }
このクラスのオブジェクトは、周囲の温度に応じて、3つの気象条件の1つを1つのデバイスに表示するように設計されています。 コードを書いている時点では、結果を表示するデバイスも温度センサーも使用できず、それらにアクセスしようとすると、プログラムが失敗する可能性があります。
最も単純なケースでは、ロジックを検証するために、指定されたクラスから継承し、接続されていないデバイスにアクセスするスタブメソッドに置き換え、子孫インスタンスでユニットテストを実行できます。 PHPUnitのモックオブジェクトはほぼ同じ方法で実装されますが、組み込みAPIの形式で追加の利便性が提供されます。
モックオブジェクトの取得
Mockオブジェクトのインスタンスを取得するには、getMock()メソッドを使用します。
class MyClassTest extends PHPUnit_Framework_TestCase { public function test_process() { $mock = $this->getMock('MyClass'); // , $mock MyClass $this->assertInstanceOf('MyClass', $mock); } }
ご覧のとおり、必要なMockオブジェクトの取得は非常に簡単です。 デフォルトでは、その中のすべてのメソッドは、何もせず常にnullを返すスタブに置き換えられます。
GetMock呼び出しオプション
public function getMock( $originalClassName, // , Mock $methods = array(), // array $arguments = array(), // , $mockClassName = '', // Mock $callOriginalConstructor = true, // __construct() $callOriginalClone = true, // __clone() $callAutoload = true // __autoload() );
getMock()をnullの2番目の引数としてビルダーに渡すと、Mockオブジェクトが置換なしで返されます。
getMockBuilder
チェーンスタイルで記述することを好む人のために、PHPUnitは適切なコンストラクタを提供します。
$mock = $this->getMockBuilder('MyClass') ->setMethods(null) ->setConstructorArgs(array()) ->setMockClassName('') // , Mock "" ->disableOriginalConstructor() ->disableOriginalClone() ->disableAutoload() ->getMock();
チェーンは常にgetMockBuilder()メソッドで開始し、getMock()メソッドでダウンロードする必要があります-これらはチェーン内で必要な唯一のリンクです。
モックオブジェクトを取得する追加の方法
- getMockFromWsdl()- WSDLの記述に基づいてMockオブジェクトを構築できます。
- getMockClass()-Mockクラスを作成し、その名前を文字列として返します。
- getMockForAbstractClass()-すべての抽象メソッドが置き換えられた抽象クラスのモックオブジェクトを返します。
これはすべて素晴らしいです-あなたは言いますが、次は何ですか? これに応えて、私たちは最も興味深いことにたどり着いたと言います。
メソッド呼び出しを待っています
PHPUnitを使用すると、置換されたメソッドの呼び出しの数と順序を制御できます。 これを行うには、expects()コンストラクトを使用し、続いてmethod()を使用して目的のメソッドを使用します。 例として、記事の冒頭のクラスを見て、それについて次のテストを書いてみましょう。
public function test_process() { $mock = $this->getMock('MyClass', array('getTemperature', 'getWord', 'showWord')); $mock->expects($this->once())->method('getTemperature'); $mock->expects($this->once())->method('showWord'); $mock->expects($this->once())->method('getWord'); $mock->process(); }
process()メソッドを呼び出すときに、リストされている3つのメソッドgetTemperature()、getWord()、showWord()を1回呼び出すと、このテストの結果が成功します。 テストでは、showWord()の呼び出しがチェックされた後にgetWord()の呼び出しがチェックされますが、テストメソッドでは逆のことが当てはまることに注意してください。 そうです、ここにエラーはありません。 PHPUnitでメソッド呼び出しの順序を制御するために、別の構成(at())が使用されます。 PHPUnitがメソッド呼び出しのシーケンスを同時にチェックするように、テストコードを少し修正しましょう。
public function test_process() { $mock = $this->getMock('MyClass', array('getTemperature', 'getWord', 'showWord')); $mock->expects($this->at(0))->method('getTemperature'); $mock->expects($this->at(2))->method('showWord'); $mock->expects($this->at(1))->method('getWord'); $mock->process(); }
PHPUnitで呼び出しの期待値をテストするための1回の()およびat()に加えて、any()、never()、atLeastOnce()およびちょうど($ count)の構造もあります。 彼らの名前は彼ら自身のために語っています。
結果のオーバーライドを返す
はるかに、Mockオブジェクトの最も便利な機能は、スプーフィングされたメソッドによって返された結果をエミュレートする機能です。 クラスのprocess()メソッドに戻ります。 温度センサーへのアピールがあります-getTemperature()。 しかし、実際にはセンサーがないことも覚えています。 たとえそれがあったとしても、考えられるすべての状況をテストするために、15度以下に冷却したり、25度以上に熱したりすることはありません。 ご想像のとおり、この場合、モックオブジェクトが役立ちます。 will()を使用して、目的のメソッドに必要な結果を返すようにできます。 以下に例を示します。
/** * @dataProvider provider_process */ public function test_process($temperature) { $mock = $this->getMock('MyClass', array('getTemperature', 'getWord', 'showWord')); // getTemperature() $temperature $mock->expects($this->once())->method('getTemperature')->will($this->returnValue($temperature)); $mock->process(); } public static function provider_process() { return array( 'cold' => array(10), 'warm' => array(20), 'hot' => array(30), ); }
明らかに、このテストは、テストクラスが処理できるすべての可能な値を対象としています。 PHPUnitは、will()で使用する次の構成要素を提供します。
- returnValue($値)-$値を返します。
- returnArgument($ index)-メソッドが呼び出されたときに指定された番号$ indexを持つ引数を返します。
- returnSelf()-チェーンメソッドのテストに役立つ、自身へのポインタを返します。
- returnValueMap($ map)-特定の引数セットに基づいて結果を返すために使用。
- returnCallback($コールバック)-指定された関数に引数を渡し、その結果を返します。
- onConsecutiveCalls()-リストされた引数の1つを、後続の各メソッド呼び出しで順番に返します。
- throwException($ exception)-指定された例外をスローします。
引数を検証する
テスト用のMockオブジェクトのもう1つの便利な機能は、with()構造を使用してスプーフィングされたメソッドを呼び出すときに指定された引数をチェックすることです。
public function test_with_and_will_usage() { $mock = $this->getMock('MyClass', array('getWord')); $mock->expects($this->once()) ->method('getWord') ->with($this->greaterThan(25)) ->will($this->returnValue('hot')); $this->assertEquals('hot', $mock->getWord(30)); }
()の引数は、assertThat()チェックとすべて同じ構造を取ることができるため、ここでは詳細な説明なしに可能な構造のリストのみを提供します。
- 属性()
- 何でも()
- arrayHasKey()
- 含む()
- equalTo()
- attributeEqualTo()
- fileExists()
- より大きい()
- greaterThanOrEqual()
- classHasAttribute()
- classHasStaticAttribute()
- hasAttribute()
- 同一()
- isFalse()
- isInstanceOf()
- isNull()
- isTrue()
- isType()
- lessThan()
- lessThanOrEqual()
- matchesRegularExpression()
- stringContains()
これらの構造はすべて、論理構造logicalAnd()、logicalOr()、logicalNot()、logicalXor()を使用して組み合わせることができます。
$mock->expects($this->once()) ->method('getWord') ->with($this->logicalAnd($this->greaterThanOrEqual(15), $this->lessThanOrEqual(25))) ->will($this->returnValue('warm'));
PHPUnitのMockオブジェクトの機能に完全に慣れたので、クラスの最終テストを実行できます。
/** * @dataProvider provider_process */ public function test_process($temperature, $expected_word) { // Mock , getWord() process() $mock = $this->getMock('MyClass', array('getTemperature', 'showWord')); // getTemperature() $temperature $mock->expects($this->once())->method('getTemperature')->will($this->returnValue($temperature)); // , showWord() $expected_word $mock->expects($this->once())->method('showWord')->with($this->equalTo($expected_word)); // $mock->process(); } public static function provider_process() { return array( 'cold' => array(10, 'cold'), 'warm' => array(20, 'warm'), 'hot' => array(30, 'hot'), ); }
UPD: VolCh は 、上記のようなテストの作成はアンチパターンであると正しく述べました。 したがって、上記の例は、PHPUnitのMockオブジェクトの機能に慣れるためにのみ考慮されるべきです。
静的メソッドの置換
PHPUnitバージョン3.5以降では、静的構築staticExpects()を使用して静的メソッドを置き換えることができます。
$class = $this->getMockClass('SomeClass'); // PHP 5.3 // call_user_func_array() $class::staticExpects($this->once())->method('someStaticMethod'); $class::someStaticMethod();
この技術革新を実際に適用するには、テスト対象クラス内で、交換可能な静的メソッドの呼び出しが次のいずれかの方法で発生する必要があります。
- $ this-> staticMethod()-動的メソッドから。
- static :: staticMethod()-静的および動的メソッドから。
selfの制限により、この方法でクラス内で呼び出される静的メソッドの置換は機能しません。
self::staticMethod();
おわりに
結論として、テストされたクラスを外部データソースから隔離する以外の目的で、Mockオブジェクトに夢中になりすぎないようにする必要があります。 それ以外の場合、ソースコードにわずかな変更を加えても、テスト自体も同様に編集する必要があります。 かなり重要なリファクタリングは、完全に書き直さなければならないという事実につながる可能性があります。