🐘

PHPならPhakeで快適モックライフ!

2020/09/25に公開

なぜ PHPUnit を使わないか

PHP でテストといえば、おなじみ PHPUnit。モック・スタブも実装されており、PHPUnit さえ使っていれば困ることもなく…。

いえ、私は困りました。PHPUnit のモック、すこし記述が冗長になるのが欠点。CakePHP といったフレームワークを横断的にテストする際、テストコードが整然と並ばず複雑な入れ子になったとき、これはまずいと直感し他の技術を探し求めたのです。

そこで知ったのが Phake フレームワーク。Java のモック・フレームワーク"Mockito"が由来だそうです。

すでに Phake に関する記事はいくつか書いていますが、今一度まとめておきます。

CakePHP に Phake を導入するノウハウ

CakePHP はテストクラスが PHPUnit に依存しているため、他のフレームワークの介入には工夫が必要でした。それらは過去の記事でまとめています。

Phake リファレンスからいくつか覚え書き

Phake は PHP 5.2.4 以上で使えます。公式リファレンスはこちらです。インストール手法については環境が様々なので割愛します。Composer 使えます。

モック生成

まずは基本のモック生成です。だいたいこれで済みます。

// MyClassはクラス名
$mock = Phake::mock('MyClass');

Phake::mock()だと何度やっても例外が出る、そんな時は下のPhake::partialMock()にするとうまくいくかもしれません。

パーシャルモック

上のモック生成では全てのメソッドがスタブ化されますが、パーシャルモックの場合はメソッドの振る舞いは変更されません。通常のインスタンス生成と変わりありませんが、後述のPhake::verify()といった検証メソッドで使えます。

$mock = Phake::partialMock('MyClass');

対象クラスのコンストラクタが値を必須とする場合、Phake::partialMock()の第 2 引数以降に記述します。

$mock = Phake::partialMock('MyClass', $anyValue);

スタブを定義する

スタブ定義はPhake::when()です。

$mock = Phake::mock('Item');
Phake::when($mock)->getPrice()->thenReturn(100);

上の例だと、「商品オブジェクトのモックに対してgetPrice()を呼ぶと100が返ってくる、と定義する」と読みます。

->getPrice()->この部分にメソッド名を記述するため文字列リテラルが増えず読みやすいと感じます。PHP の特徴である__call()にも対応できます。Phake::whenCallMethodWith()を使うのですが、ここでは割愛します。

振る舞い定義の種類

  • 戻り値を定義するthenReturn()
  • 例外スローを定義するthenThrow()
  • Parent を呼ぶthenCallParent()

引数の絡んだスタブ定義

$cart = Phake::mock('ShoppingCart');
Phake::when($cart)->addItem($item1)->thenReturn(1);
Phake::when($cart)->addItem($item2)->thenReturn(2);

上の例ではメソッドaddItem()に対して 2 種類のスタブを定義しています。

$cart = Phake::mock('ShoppingCart');
Phake::when($cart)->addItem(Phake::anyParameters())->thenReturn(1);

Phake::anyParameters()を使えば引数の内容は不問となります。

一括でスタブを設定

$mock = Phake::mock('MyClass', Phake::ifUnstubbed()->thenReturn(42));

Phake::mock()の第 2 引数にPhake::ifUnstubbed()とすることで、個別にスタブ定義していないメソッドが呼ばれた場合の振る舞いを一括で設定できます。

呼び出しを検証する

$mock = Phake::mock('MyClass');

$mock->fooWithArgument('foo');
$mock->fooWithArgument('bar');

// 上の2行の振る舞いについて、下の2行で検証

Phake::verify($mock)->fooWithArgument('foo');
Phake::verify($mock)->fooWithArgument('bar');

上の例ではfooWithArgument()に引数'foo''bar'が与えられて呼ばれたかどうかを検証しています。

$mock->fooWithArgument('baz');
Phake::verify($mock)->fooWithArgument('foo');

上の例では「引数は'foo'のはずだが、異なった引数が与えられた」として落ちます。

$mock->fooWithArgument('baz');
Phake::verify($mock)->fooWithArgument(Phake::anyParameters());

Phake::anyParameters()を使うと引数の内容は不問になります。呼ばれたか、呼ばれていないかだけを検証します。

$any = Phake::anyParameters();

$mock->fooWithArgument('baz');
Phake::verify($mock)->fooWithArgument($any);

Phake::anyParameters()はオブジェクトを返すだけなので、長いときは事前に変数に入れておくと扱いやすいです。

呼び出し回数を検証する

Phake::verify($mock, Phake::times(2))->fooWithArgument('foo');

Phake::verify()の第 2 引数に渡すPhake::times($n)で「$n回呼ばれるはず」と定義できます。第 2 引数を省略した場合はtimes(1)と同義です。

  • 「少なくとも$n回は呼ばれるはず」を定義するPhake::atLeast($n)
  • 「何度呼ばれても最大$n回までである」を定義するPhake::atMost($n)

なども用意されています。Phake::never()times(0)と同義ですが、読みやすくなるかもしれません。お好みで。

順序を検証する

Phake::inOrder(
    Phake::verify($mock)->fooWithArgument('foo'),
    Phake::verify($mock)->fooWithArgument('bar')
);

Phake::inOrder()で括ることで順序も検証できます。個人的にはテストのメンテナンス性などを考えるとやや煩雑に思います。お好みで。

引数をキャプチャする

引数の文字列を正規表現で検証したいときや、引数の配列を検証したいときなどにはPhake::capture()が活躍します。

Phake::verify($mock)->setUrl(Phake::capture($param));
$this->assertThat($param, matchesRegularExpression('/.+edit.+/'));

Phake::capture($param)とすることで、以降の$paramでは「setUrl()に渡された引数」が扱えます。PHPUnit の assert メソッドが併用できるのです。変数名$paramは任意です。

上の例では与えられた URL 中に"edit"が含まれていることを検証します。

--

上記以外にもいくつか機能があるのですが、まずはwhenverifyさえ理解すればいいので、ここまでにしておきます。

Sublime Text 2 の補完定義

拙作ですが Sublime Text 2 の補完定義を作っています。テストは勢いで書くことも大事。常用するメソッドはすべて定義しています。

PHP でモックテスト やるなら Phake!

気になってきたでしょ?

Discussion