今回はダミーのオブジェクト(モックオブジェクト)を使ったテストについて見ていきます。
モックオブジェクトを使ったテスト
さて,折角完成したCartクラスですが,商品コードの代わりに商品クラス(Itemインターフェースを実装したクラス)を導入し,さらに商品の合計代金も取得できるようにすることになりました。ありがちな話ですね。具体的には,次のようなItemインターフェースが提供されています。商品コードのほかに,商品名や価格が取得可能なようです。
<?php
interface Item {
public function getName();
public function getCode();
public function getPrice();
}
しかし,肝心の実装クラス(ItemImplクラス)自身はまだ作成されていません。こういった場合,どうすればいいでしょうか?ItemImplクラスが作成されるまで待ちますか?確かに,簡単なクラスであればそれもあり得るかも知れません。しかし,対象となっているクラスが複雑な場合や時間的な制限がある場合,そうも言っていられませんね。可能であれば,Cartクラスの修正やテストを進めておきたいところです。
幸い,PHPUnit3には「モックオブジェクト」を生成する機能があります。「モック(Mock)」には「模造品」「偽の」といった意味がありますが,これを使うことで「まだ存在しない」クラスのインスタンスを使ったテストが可能になります。
次に,PHPUnitで提供されているモックオブジェクトの生成機能について説明します。
PHPUnit3のモックオブジェクト生成機能
PHPUnitはPHPのリフレクション機能を使ってモックオブジェクトを生成します。また,生成されるオブジェクトにメソッドを定義したり,そのメソッドの振る舞いを指定することができます。さらに,「制約」と呼ばれるクラス(PHPUnit_Framework_Constraintのサブクラス)を使って,期待する回数だけメソッドがコールされたかどうかや,期待した振る舞いをしているかどうかのテストも可能です。
それでは,具体的な生成手順を見ていくことにします。
モックオブジェクトの生成は,PHPUnit_Framework_TestCaseクラスのgetMockメソッドを呼び出すだけです。getMockメソッドのAPIとパラメータについては,以下のようになっています。
object getMock($className, [array $methods, [array $arguments, [string $mockClassName]]])
| パラメータ | 必須 | 意味 |
|---|---|---|
| $className | ○ | このクラスのモックオブジェクトを生成する |
| array $methods | 生成するメソッド | |
| array $arguments | コンストラクタの引数 | |
| string $mockClassName | モックオブジェクト自身のクラス名。デフォルトは,Mock_[$className]_[生成時刻(ミリ秒)のハッシュ値] |
最も簡単なコードは以下のようになります。
$mock = $this->getMock('Item');
このコードを実行すると内部では以下のようなコードが生成され,そのインスタンスが返されます。以下は,生成されるモックオブジェクトのソース例です。
class Mock_Item_fe459e06 extends Item implements PHPUnit_Framework_MockObject_MockObject {
private $invocationMocker;
public function __construct() {
$this->invocationMocker = new PHPUnit_Framework_MockObject_InvocationMocker($this);
}
public function __clone() {
$this->invocationMocker = clone $this->invocationMocker;
}
public function getInvocationMocker() {
return $this->invocationMocker;
}
public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) {
return $this->invocationMocker->expects($matcher);
}
public function verify() {
$this->invocationMocker->verify();
}
}
お気づきの通り,実際に生成されるオブジェクトは,$classNameで指定したクラスのサブクラスとなります。
メソッドを指定してモックオブジェクトを生成する場合,getMockメソッドの第2引数にメソッド名を配列で指定します。
$mock = $this->getMock('Item', array('getName', 'getCode', 'getPrice'));
内部で生成されるコードは以下のようになり,第2引数で指定したメソッドが生成されていることが分かると思います。以下は,生成されるモックオブジェクトのソース例です。
class Mock_Item_23571fea extends Item implements PHPUnit_Framework_MockObject_MockObject {
private $invocationMocker;
public function __construct() {
$this->invocationMocker = new PHPUnit_Framework_MockObject_InvocationMocker($this);
}
public function __clone() {
$this->invocationMocker = clone $this->invocationMocker;
}
public function getInvocationMocker() {
return $this->invocationMocker;
}
public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) {
return $this->invocationMocker->expects($matcher);
}
public function verify() {
$this->invocationMocker->verify();
}
public function getName() {
$args = func_get_args();
return $this->invocationMocker->invoke(
new PHPUnit_Framework_MockObject_Invocation($this, "Item", "getName", $args)
);
}
public function getCode() {
$args = func_get_args();
return $this->invocationMocker->invoke(
new PHPUnit_Framework_MockObject_Invocation($this, "Item", "getCode", $args)
);
}
public function getPrice() {
$args = func_get_args();
return $this->invocationMocker->invoke(
new PHPUnit_Framework_MockObject_Invocation($this, "Item", "getPrice", $args)
);
}
}

