Laravel4、静的メソッドとテスタビリティー

タグ: Laravel4  

Laravelは読み書きしやすいフレームワークです。その理由の一つはコアが提供しているメソッドがスタティックであることです。

スタティックメソッドはそのクラスのオブジェクトがなくても使用できます。オブジェクトが存在すれば、ちょっとした仕組みでオブジェクトを入れ替えることができますが、肝心のオブジェクトがなくても動かせるスタティックメソッドだと置き換えが面倒です。

置き換えられないと、あるクラスを作成している途中やテストの時にちょっとしたダミープログラムと置き換えたり、機能強化の時に新しいクラスオブジェクトに変更したりできません。

そこでLaravel3ではIoCコンテナが導入され、Laravel4ではさらにコンストラクターのタイプヒントにより、自動的にオブジェクトを生成してくれる仕組みが採用されました。

これで自作するクラスに関しては、依存注入によるクラスの入れ替えは簡単になりました。

自作しない部分、つまりLaravel4のコアクラスについてはどうでしましょうか。静的メソッドで機能が提供されています。また、app.phpでエイリアスも登録され、短く分かりやすい名前でアクセスできます。

それをIoCコンテナ機能を使用し、オブジェクトからアクセスするようにしてしまえば、読み書きのしやすさ、使い勝手の良さを損なうことになります。

厳密に依存注入を適用するならコアクラスもこうした依存注入を行ったほうが良いのでしょう。ですがフレームワークを使用する目的、有利な点として読み書きしやすさでLaravelを選んだ人達には受け入れがたいものです。

ですから、コアはFacadeクラスを継承した、ラップクラスを通じてアクセスするようになっています。これにより、読み書きしやすいスタティックで記述しつつ、その実態はオブジェクトがインスタンス化され、十分にテストしやすくなっています。

更に、Laravel4ではベータの途中から、モッキングフレームワークのMockeryを使う解決法を取り入れました。Mockeryは強力なため、単独でもコアクラスの置き換えはできます。ですが通常多少のセットアップ(依存注入など)の手間がかかります。そこで、簡単に使用できるようになったわけです。

Mockeryの翻訳ドキュメントは、時間に余裕が出きましたら見直し、このサイトで公開します。(Laravel4の電子本には荒訳版が入っています。現在、Laravelに関する電子本を2冊翻訳中です。それが終わってからになります。)

具体的にはコアクラスに、Mockeryへどのメソッドを取り扱うかを指定するshouldRecive()関数を使用すると、Mockeryのモックオブジェクトが取得でき、引き続きチェーンで制約や期待値を指定できるようになりました。

Input::shouldRecive('all') ... // Input::allをモックする
View::shouldRecive('make') ... // View::makeをモックする

具体的に見てみましょう。Abcモデルを考えます。ファイルを取り込み、その内容をリターンします。

<?php
class Abc {
    public static function aaa() {
        $file = File::get('filename');
        return $file;
    }
}

filenameというファイル名を読み込み、その内容をリターンするだけのメソッドaaaを提供しています。

これをテストしようとすると、通常ならfilenameを作成し、その内容をチェックします。しかしこのaaaを単体でテストしたいのであり、File::getメソッドのテストを行いたいわけでありません。

ユニットテストとして、File::getには関わらず、テスト可能にするには、Fileのインスタンスを外部から注入できるようにする必要があります。

裏ワザ的な方法で、当サイトではLaravel3でもコアクラスのエイリアスを登録し直し、置き換える手法を紹介していました。Laravel4なら、そのような裏技的な手法を取ることはありません。(正直、Mockeryを使用すれば、Laravel3でも可能です。多少手間はかかりますが、たいしたことはありません。)

テストコードはこのようになります。

<?php

class AbcTest extends TestCase {

    public function testMockeyExample()
    {
        File::shouldReceive('get')->once()->withAnyArgs()->andReturn(array('テスト<br>', 'そのに'));

        $line = Abc::aaa();

        $this->assertEquals($line, array('テスト<br>', 'そのに'));
    }
}

shouldReceiveでモックとして取り扱うメソッド名を指定しています。この場合はgetです。

onceでgetの実行回数を宣言しています。この場合一回です。実行しなかったり、2回以上実行すると宣言した実行回数と異なるため、例外が発生し、テストは失敗します。

withAnyArgsでgetでどの引数が渡されても、このモックが動くようにしています。withで細かく引数を指定すれば、引数によりリターンされる値を変更することが可能です。

andReturnでgetの返却値を指定します。この場合は読み込んだファイルの内容をシミュレートするため、適当な配列を返しています。

次の行で実際にAbcクラスのaaaメソッドを呼び出しています。

最後は通常のPHPUnitのアサートです。シミュレートしたファイルの読込結果と、aaaの戻り値を比較します。

このような形式でお手軽にコアクラスのメソッドをモックに出来ます。ユニットテストがガンガン捗ることでしょう。

なお、この機能を使用する場合、MockeryをComposerでインストールする必要があります。もちろん、composer.jsonにパッケージの指定を書き加え、composer updateを実行するだけです。

実はLaravel3でオレオレ拡張をして、テスタビリティーを上げ、定型的な記述も減らせる仕組みを作っていたんです。コントローラーをほとんど例外の処理でかけるようにしたりしていたのは、その一環だったんです。80%くらいはできたんで、ドキュメントを書こうと思いましたが、内容が多岐にわたるため、3度くらい書き直していました。そこで数回に分けて記事を書いていました。ですが、Laravel4の新しい機能拡張と、テスタビリティーを上げる工夫が素晴らしいので、それ以上作成するのはやめました。

テスト環境が使いやすいと、テストを書くのも楽しいですよ。

Happy testing!