PhakeのモックでCakePHPをテスト モデル篇
みなさんテストしてますか? 簡単な assert テストはスラスラ書けても、依存関係が絡む Controller や Model 周りのテストは一筋縄ではいかないことが多いです。
自分には無縁(見て見ないふり)だったものの、最近いよいよ「これはしっかりテストしないと…」という状況に追い込まれモックの学習を始めました。
CakePHP でのモック
CakePHP では PHPUnit が標準で採用され、CakePHP 上で扱い易いようCakeTestCase
やControllerTestCase
といったサブクラスが用意されています。
PHPUnit にはモック・フレームワークも備わっており、そのままでもモック/スタブを利用できるのですが、なんか書けば書くほど面倒…と意気消沈。もっと書きやすい爽快なモック・フレームワークは無いかと探し、そこで見つけたのがPhakeです。
PHPUnit での Model モック/スタブ生成
前振りが長くなりました。PHPUnit と Phake のモック生成の違いについて具体例を載せます。
$mock = $this->getMockForModel('Post', ['save'])
$mock->expects($this->once())
->method('save')
->will($this->returnValue(true));
// 以下、$mockを使ったテスト処理
getMockForModel()
は CakePHP が拡張した PHPUnit モック生成メソッドです。モデル名"Post"、スタブ化したいメソッド名"save"を与えたあと、
-
->expects()
で呼ばれうる回数 -
->method()
で対象メソッド名 -
->will($this->returnValue())
でテスト中に"save"が呼ばれた時の戻り値を定義します。
改行しても行数をとり、一行にしてもやたらと長いというのが難点。
$mock->expects($this->once())->method('save')->will($this->returnValue(true));
さらに、長すぎるので例は載せませんがreturnValue()
の値が別のモック・オブジェクトだったりすると定義はどんどん膨らみます。(私はこれで嫌になりました)
Phake での Model モック/スタブ生成
続いて Phake の Model モック生成とスタブ定義について紹介します。
$mock = Phake::mock('Post');
Phake::when($mock)->save(Phake::anyParameters())->thenReturn(true);
// 間に$mockを使ったテスト処理
Phake::verify($mock)->save(Phake::anyParameters());
個人的に Phake がいいなって思えたのはwhen()
とverify()
という記法でした。
- 先にスタブの動作を定義
- 次にテスト実行
- 最後に何回呼ばれたかを検証
という上から順を追える書き方に好感が持てます。この記法は Java のモック・フレームワーク"Mockito"が由来だそうです。
もう一点、スタブメソッド名の指定が文字列ではなく->doSomething()->
といった記法でメソッド名として書ける点も特徴です。多くの場合エディタのカラーリングは文字列とメソッド名で異なるため、目に優しい気もします。
Phake::verify($mock)->doSomething(); // この場合暗黙でtimes(1)
Phake::verify($mock, Phake::times(2))->doSomething(); // 直接指定
Phake::verify($mock, Phake::never())->doSomething(); // times(0)と等しい
Phake::verify($mock, Phake::atLeast(2))->doSomething();
Phake::verify($mock, Phake::atMost(2))->doSomething(); // いろいろ範囲指定できます
スタブがモック・オブジェクトを返す例
->thenReturn()
で別のモック・オブジェクトを返す場合も簡潔です。
$any = Phake::anyParameters();
$mock = Phake::mock('FooObject');
Phake::when($mock)->something($any)->thenReturn(true);
$model = Phake::mock('Post');
Phake::when($model)->fooObject()->thenReturn($mock);
// 間に$modelを使ったテスト処理
Phake::verify($model)->fooObject();
Phake::verify($mock)->something($any);
PHPUnit で同じことをやると、ネストが深くなるか無闇に長くなるかのどちらかです。そして定義ブロックとテストブロックが離れてしまうので、行が増えるほど目で追いにくくなります。上から順に進む Phake は親切かな。
※Phake::anyParameters()
はテスト中、引数を問わない際に使う特殊な Phake オブジェクトですが、毎回書くと長いので変数に格納しても大丈夫です。
Phake::Capture()は便利
最後にPhake::Capture()
をお勧めします。CakePHP では文字列や配列を引数として与えるケースが多いですが、引数の文字列の一部を正規表現で検証したいときや、引数の配列を検証したいときなどにCapture()
は活躍します。
// モック生成とスタブ定義
// テスト処理
Phake::verify($mock)->setUrl(Phake::capture($param));
$this->assertThat($param, matchesRegularExpression('/.+edit.+/'));
Phake::capture($param)
とすることで、以降の$param
では「テスト中にsetUrl()
に渡された文字列」が扱えます。PHPUnit 標準の assert メソッドが併用できるのです。突如$param
が出てくるのがちょっと不思議に見えますが問題ありません。変数名も任意です。
Phake は書きやすい
以上、露骨な PHPUnit 下げと Phake 上げになりました! テストはストレスを感じてしまうと続かないので、書きやすい方法を今後も探っていきたいです。Sublime Text で Phake をガンガン書ける補完も作成しましたので、ぜひとも。
長くなるので続きます。次はコントローラ篇。それでは。
--
2014/5/9 追記: コンポーネント篇も書きました。Component を単独でテストしたい場合はこちらです。
Discussion