PHP
CakePHP
Phake
PHPUnit

PhakeのモックでCakePHPをテスト モデル篇

More than 5 years have passed since last update.

みなさんテストしてますか? 簡単なassertテストはスラスラ書けても、依存関係が絡むControllerやModel周りのテストは一筋縄ではいかないことが多いです。

自分には無縁(見て見ないふり)だったものの、最近いよいよ「これはしっかりテストしないと…」という状況に追い込まれモックの学習を始めました。


CakePHPでのモック

CakePHPではPHPUnitが標準で採用され、CakePHP上で扱い易いようCakeTestCaseControllerTestCaseといったサブクラスが用意されています。

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を単独でテストしたい場合はこちらです。