PHPUnit の Expectation を簡潔に書く TIPS

PHPUnit の小ネタです。

PHPUnit でモックを使ったテストケースを書くと、以下のように記述量が多くなってうんざりします。

<?php
class FooClassTest extends PHPUnit_Framework_TestCase {
    /**
     * @covers FooClass::doSomething
     */
    public function testDoSomething() {
        // FooClass が依存している BarClass のモックを作る。
        // 何もしないように、__construct() と __clone() は無効化しておく。
        $mockBar = $this->getMockBuilder('BarClass')
            ->disableOriginalConstructor()
            ->disableOriginalClone()
            ->getMock();

        // BarClass::barMethod のモックの挙動を変える。
        $mockBar->expect($this->any())
            ->method('barMethod')
            ->will($this->returnValue(true));

        $object = new FooClass($mockBar);

        $this->assertTrue($object->doSomething());
    }
}

長い、書きづらい、読みづらい。こんなテストケース、2つ以上書きたくないですね。もっと楽に書くための TIPS をメモしておきます。

まず、何もしない「プレーンな」モックオブジェクトを作るメソッドを作りましょう。PHPUnit_Framework_TestCase を継承した MyTestCase に実装してやります。

class MyTestCase extends PHPUnit_Framework_TestCase {

    /**
     * 元のクラスが完全に何もしないよう擬装したモックを作成する。
     *
     * - コンストラクタが無効
     * - クローンコンストラクタが無効
     *
     * @param string $class_name
     * @return mixed 指定したクラスのモック
     * @throws InvalidArgumentException 指定したクラス名が見つからない場合
     */
    protected function _getPlainMock($class_name) {
        if (!class_exists($class_name)) {
            throw new InvalidArgumentException("{$class_name} does not exist.");
        }

        return $this->getMockBuilder($class_name)
            ->disableOriginalConstructor()
            ->disableOriginalClone()
            ->getMock();
    }

}

また、PHPUnit の Expectation はメソッドチェーンで連続して指定できます (“->” 演算子で繋げて書ける) が、そもそも記述量が多いです。いちいち this this 書くのも煩わしい。

そこで、Expectation をセットするためのメソッドを作ってやると楽になります。

<?php
    /**
     * モックオブジェクトに Expectation を設定する。
     *
     * @param Object &$mock モックオブジェクト
     * @param string $matcher マッチャー ([any|once|never|...])
     * @param string $method メソッド名
     * @param mixed $return_value 期待する戻り値
     */
    protected function _setExpectation(&$mock, $matcher, $method, $return_value = null) {
        $mock->expects($this->$matcher())
            ->method($method)
            ->will($this->returnValue($return_value));
    }

これを使うと、一番最初のテストケースは以下のように書き直せます。

require_once 'MyTestCase.php';

class FooClassTest extends MyTestCase {
    /**
     * @covers FooClass::doSomething
     * @test
     */
    function doSomething() {
        $mockBar = $this->_getPlainMock('BarClass')
        $this->_setExpectation($mockBar, 'any', 'barMethod', true);

        $object = new FooClass($mockBar);

        $this->assertTrue($object->doSomething());
    }
}

簡単になりましたね!9行もあったコードが4行に減りました。

ちなみに、さり気なくテストケースの public は削除しています。PHP の仕様上、public なメソッドはわざわざそれを書く必要がありません。

また、PHPDoc 部分に @test アノテーションを付けると、メソッド名を test で始めなくてもテストケースとして認識されるようになります。私はこの方が読みやすいので、最近はこう書くことが多いです。

こういう改善をしても、Ruby に慣れているとまだ長く感じます。「$」「’」「>」などの記号を打つのも煩わしいです。Ruby 使いてえなあ。

参考