『次世代 Mock Object フレームワークの雄』として一部から熱く注目されている(と思いたい)
PHPMock ですが、ちょろっとテストコード読んだくらいで触った事が無かったのでトライ。
ちなみに PHPMock のテストコードは PHPSpec で書かれている。個人的には残念な書き味というか、読みやすくない。PHPUnit バンザイ。
ただ PHPUnit の Mock はもうちょっと融通利かせてもらえると有難い。という所で PHPMock はどうか。
Let’s do this.
PHPMock にはまともなドキュメントが一切無い。tgz 玉もないので
Google Code からチェックアウトするしかない。specs ディレクトリにテストコードがあるのでこいつを見れば大体の使い方はわかる。test as document を地で行く感じだ。メソッド名は RSpec の mock/stub に似ているというか似せてある。
テストコードはこんな感じ。
ダウンロードはこちら。
■ test/ProductServiceTest.php
<?php
define(DEMO_ROOT, dirname(dirname(_FILE_)));
require_once DEMO_ROOT . '/phpmock/src/PHPMock.php';
require_once DEMO_ROOT . "/lib/ProductService.php";
require_once DEMO_ROOT . '/lib/IDBConnection.php';
require_once DEMO_ROOT . '/lib/Publisher.php';
class ProductServiceTest extends PHPUnit_Framework_TestCase
{
const PRODUCT_ID = 1234;
public function test_プロダクトを全件取得する ()
{
$mockDb = PHPMock::mock('DBConnection');
$mockDb->shouldReceive('execute')
->withAnyArgs()
->once()
->andReturn(
array('name' => 'NINJA GAIDEN 2')
);
$this->service->setDBConnection($mockDb);
$this->assertNotNull($this->service->getAll());
}
public function test_プロダクトIDからパブリッシャの名前を取得する()
{
$mockPublisher =
$this->createPublisherMock(self::PRODUCT_ID);
$this->service->setPublisher($mockPublisher);
$publisher = $this->service->
getPublisherByProductId(self::PRODUCT_ID);
$this->assertEquals('Activision', $publisher['name']);
}
public function test_クイックモックでfindAllしてみる()
{
$mockPublisher = PHPMock::mock('Publisher',
array('findAll' => array()));
$mockPublisher->findAll();
$this->assertTrue($mockPublisher->verify());
}
public function test_引数がマッチしなければ例外を投げる()
{
$mockPublisher =
$this->createPublisherMock(self::PRODUCT_ID);
try {
$mockPublisher->findByProductId(9999);
} catch (Exception $e) {
return;
}
$this->fail("An expected exception has not been raised.");
}
public function test_PHPUnitのMockObjectはこんな感じ()
{
$mock = $this->getMock('DBConnection',
array('execute', 'preparedStatement'));
$mock->expects($this->once())
->method('execute')
->will($this->returnValue('aaaa'));
$this->assertEquals('aaaa', $mock->execute("sql statement"));
}
public function createPublisherMock ($productId)
{
$mock = PHPMock::mock('Publisher');
$mock->shouldReceive('findByProductId')
->with($productId)
->andReturn(
array('name' => 'Activision')
);
return $mock;
}
public function setUp ()
{
$this->service = new ProductService;
}
public function tearDown ()
{
}
}
で、このコードは実行すると「test_引数がマッチしなければ例外を投げる」でコケる。
PHPMock_Expectation#matchArgs に問題があって
■ phpmock/src/PHPMock/Expectation/Expectation.php
public function matchArgs(array $args)
{
if (empty($args) && empty($this->_expectedArgs) && is_array($this->_expectedArgs)) {
return true;
} elseif ($args == $this->_expectedArgs) {
return true;
} elseif ($this->_expectedArgs == true) {
return true;
} elseif ($this->_expectedArgs == false && empty($args)) {
return true;
}
return false;
}
「$this->_expectedArgs
== true」これだと $this->_expectedArgs に値が入ってれば true になっちゃうという良く話題になるアレです。正解は「$this->_expectedArgs
=== true」これですな。
上のコードで使っている DBConnection はインターフェースで Publisher は空っぽのクラス。
インターフェースに定義されているメソッドは PHPMock#_generateDefForMethods がよしなに実装してくれるので安心。PHPUnit の Mock Object だと定義したメソッドをイチイチ配列で全部ツッコまないといけない。
いわゆるスタブは無くて shouldReceive 一本になると思われる。
PHPMock#mock の第二引数に array($methodName => $returnValue) を与える事で簡単にモックが作れるのは好感。
■ まとめ
- まだまだ挙動が怪しい点はある
- それでも PHPUnit の mock よりは楽できる
- テストコード内でザクザクモック作りたいなら MockInterceptor よりオススメ