こういう処理があるとします
/**
* キャッシュにdataがあればそれを返却
* なかったらDBからのdataを取ってきてから返却
* @return array
*/
public function get()
{
$dataFromCache = $this->getCachedData();
if (!empty($dataFromCache)) {
return $dataFromCache;
}
$dataFromDb = $this->getDataFromDb();
if (!empty($dataFromDb)) {
return $dataFromDb;
}
throw new \RuntimeException('no data anywhere');
}
テストしたい事
テストしたいのは、この処理の主な仕事である「キャッシュされたdataがなかったら、DBからのdata取得をする」こと自体ができているかどうかです。
実際にDBにある値をちゃんと取得できているかどうかは、 getDataFromDb() をテストすることで確認するので、今はそこは見なくていいです。
今までよく書いていたテストコード
大体、こんな流れのUTを書いていました:
- getCachedData() の戻り値が空になるような状態を作り
- キャッシュファイルを空で作り成すか消す
- getDataFromDb() が呼ばれて結果で get() の返り値として事前にDBにセットしたdataが取得されているかを確認する
テスト実行環境にそもそもDBも必要ですし、DB内にテストデータを事前に用意しないといけないなど、手間が結構かかるやり方です。
今回覚えた新しい方法
社内でやった勉強会で別のやり方もあると聞いてやってみたら、UTの書く手間も実行コストも少なそうと思ったので、備忘録を残すことにしました。
どうやってテストするかというと:
- get()の内部で呼ばれている2つのメソッドをモックします
- ちゃんと2つ目のメソッドに処理がたどり着くような条件を作ります
- 具体的に、getCachedData() の返り値が空になるようにします
- getDataFromDb() が呼ばれたかどうかだけを確認します
テストコードの例はこちら:
/**
* @test
* @covers get()
*/
public function testIfGetCallsLastMethod()
{
$fakeClass = $this->getMockBuilder('MyClass')
->setMethods(['getCachedData', 'getDataFromDb'])
->getMock();
//ここで getCachedData() の返り値が空になるようにしておく
$fakeClass->method('getCachedData')->willReturn([]);
//getDataFromDb()から何も返されないとExceptionが発生するのでなにかを返す
$fakeClass->method('getDataFromDb')
->willReturn(['param' => 'result']);
// getDataFromDb()が1回呼ばれたことを期待するとして、get()を実行させる
$fakeClass->expects($this->once())->method('getDataFromDb');
$fakeClass->get();
}
上記を実行するに使った環境
- php 7.0.2
- PHPUnit 5.2.3
ちなみに今回は Mock を使っていますが、 Prophecy というライブラリもあってそれも面白そうで今度やってみようかなと思います。
今回は expects($this->once())
を使って「一回実行されたか」をチェックしているのですが、Loop 内でイテレーション回数に応じた実行回数をテストする英語の記事をネットで見かけたのでそれも今度試そうかなと思います。