🍰

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

2020/09/25に公開

みなさんテストしてますか? 簡単な 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 を単独でテストしたい場合はこちらです。

Discussion