PHPUnit3で始めるユニットテスト

第4回 モックオブジェクトを使ったテスト

この記事を読むのに必要な時間:およそ 11.5 分

今回はダミーのオブジェクト(モックオブジェクト)を使ったテストについて見ていきます。

モックオブジェクトを使ったテスト

さて,折角完成した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)
        );
    }
}

著者プロフィール

下岡秀幸(しもおかひでゆき)

PHP関連の情報サイト「Do You PHP?」の管理人。PHP歴は長いが,あまり仕事で使ったことがないという噂がある。最近はα版のPEAR・PECLに手を出しては地雷を踏んでいることが多い。

URLhttp://www.doyouphp.jp/http://d.hatena.ne.jp/shimooka/

コメント

コメントの記入