PhpStorm 7 が PHPUnit 4 をサポートしていなかったので少し前まで PHPUnit は 3.7 を使っていましたが、9 月に PhpStorm 8 がリリースされ、PHPUnit 4 も動作するようになったので、そろそろ使っていこうかと思います。
なお、PhpStorm 7 で PHPUnit 4 が使えなかったのは次の理由だそうです。
PHPUnit_Framework_TestListener インターフェイスに addRiskyTest() が追加されました。 PHPUnit_Framework_TestListener インターフェイスを実装するクラスは、新たにこのメソッドを実装する必要があります。 たとえば PHPStorm 7 で PHPUnit 4 が使えないのは、これが原因です。
PHPUnit 3.7 から現在の安定版である 4.3 まで、ざっくりとどんな機能が増えているか見てみました。なお、網羅はしていません、気になったものだけです。
Test Proxies と will***()
テストダブルで Test Proxies がサポートされました。
これまでは PHPUnit で作成したテストダブルは will()
で指定しない限りメソッドは null しか返しませんでした。
下記の例のように、モックビルダーで enableProxyingToOriginalMethods()
を呼んでおくと、テストダブルのメソッド呼び出しで元のオブジェクトのメソッドが呼ばれるようになります。
<?php
class Hoge
{
public function one()
{
return 1;
}
public function two()
{
return 2;
}
}
class HogeTest extends PHPUnit_Framework_TestCase
{
public function test()
{
$obj = $this->getMockBuilder(Hoge::class)
->enableProxyingToOriginalMethods()
->getMock();
$obj->expects($this->once())->method('one');
$obj->expects($this->once())->method('two');
assertThat($obj->one(), equalTo(1));
assertThat($obj->two(), equalTo(2));
}
}
ただし、will()
による指定は無視されるようです。
<?php
class HogeTest extends PHPUnit_Framework_TestCase
{
public function test()
{
$obj = $this->getMockBuilder(Hoge::class)
->enableProxyingToOriginalMethods()
->getMock();
$obj->expects($this->once())->method('one');
$obj->expects($this->once())->method('two')->willReturn(9);
assertThat($obj->one(), equalTo(1));
assertThat($obj->two(), equalTo(9)); // Failed asserting that 2 matches expected 9.
}
}
私はモッキングフレームワークに Phake も使うことがあるのですが、Phake なら次のように書けます。
<?php
class Hoge
{
public function one()
{
return 1;
}
public function two()
{
return 2;
}
}
class HogeTest extends PHPUnit_Framework_TestCase
{
public function test()
{
$obj = Phake::mock(Hoge::class);
Phake::when($obj)->one()->thenCallParent();
Phake::when($obj)->two()->thenReturn(9);
assertThat($obj->one(), equalTo(1));
assertThat($obj->two(), equalTo(9));
Phake::verify($obj)->one();
Phake::verify($obj)->two();
}
}
それと、上のほうの例でさらっと使っていますが、テストダブルで willReturn()
のような will***
の形式のメソッドが使えるようになりました。次の2つは同じ意味です。
$proxy->expects($this->once())
->method('doSomethingElse')
->willReturn(123);
$proxy->expects($this->once())
->method('doSomethingElse')
->will($this->returnValue(123));
@before
@after
@beforeClass
@afterClass
アノテーション
次のアノテーションが追加されています。
@before
@after
@beforeClass
@afterClass
これまでの setUp()
や tearDown()
や setUpBeforeClass()
や tearDownAfterClass()
と同じ機能ですが、メソッド名は何でも良くてアノテーションだけ書いておけば自動的に呼ばれます。
既存の setUp()
や tearDown()
などもそのまま使うことができます。
<?php
class BeforeAfterTest extends PHPUnit_Framework_TestCase
{
public static function setUpBeforeClass()
{
var_dump(__METHOD__);
}
public static function tearDownAfterClass()
{
var_dump(__METHOD__);
}
protected function setUp()
{
var_dump(__METHOD__);
}
protected function tearDown()
{
var_dump(__METHOD__);
}
/**
* @beforeClass
*/
public static function beforeClass_01()
{
var_dump(__METHOD__);
}
/**
* @beforeClass
*/
public static function beforeClass_02()
{
var_dump(__METHOD__);
}
/**
* @before
*/
protected function before_01()
{
var_dump(__METHOD__);
}
/**
* @before
*/
protected function before_02()
{
var_dump(__METHOD__);
}
/**
* @afterClass
*/
public static function afterClass_01()
{
var_dump(__METHOD__);
}
/**
* @afterClass
*/
public static function afterClass_02()
{
var_dump(__METHOD__);
}
/**
* @after
*/
protected function after_01()
{
var_dump(__METHOD__);
}
/**
* @after
*/
protected function after_02()
{
var_dump(__METHOD__);
}
/**
* @test
*/
public function one()
{
$this->assertTrue(true);
}
}
$ phpunit tests/BeforeAfterTest.php
PHPUnit 4.5-dev by Sebastian Bergmann and contributors.
Configuration read from /path/to/example/tests/phpunit.xml.dist
string(33) "BeforeAfterTest::setUpBeforeClass"
string(31) "BeforeAfterTest::beforeClass_01"
string(31) "BeforeAfterTest::beforeClass_02"
string(22) "BeforeAfterTest::setUp"
string(26) "BeforeAfterTest::before_01"
string(26) "BeforeAfterTest::before_02"
string(25) "BeforeAfterTest::tearDown"
string(25) "BeforeAfterTest::after_01"
string(25) "BeforeAfterTest::after_02"
string(35) "BeforeAfterTest::tearDownAfterClass"
string(30) "BeforeAfterTest::afterClass_01"
string(30) "BeforeAfterTest::afterClass_02"
@before
や @after
は protected
以上の可視性が必要です。private
だとコケます。
<?php
class BeforeAfterTest extends \PHPUnit_Framework_TestCase
{
/**
* @before
*/
private function before_01()
{
var_dump(__METHOD__);
}
/**
* @test
*/
public function one()
{
$this->assertTrue(true);
}
}
$ phpunit tests/BeforeAfterTest.php
PHPUnit 4.5-dev by Sebastian Bergmann and contributors.
Configuration read from /path/to/example/tests/phpunit.xml.dist
Fatal error: Call to private method BeforeAfterTest::before_01() ...
@beforeClass
や @afterClass
は public static function
の必要があります。可視性が public
未満だとエラーになりますし、static
を忘れるとエラーにもならずに無視されます。
<?php
class BeforeAfterTest extends \PHPUnit_Framework_TestCase
{
/**
* @beforeClass
*/
public function beforeClass_01()
{
var_dump(__METHOD__);
}
/**
* @test
*/
public function one()
{
$this->assertTrue(true);
}
}
$ phpunit tests/BeforeAfterTest.php
PHPUnit 4.5-dev by Sebastian Bergmann and contributors.
Configuration read from /path/to/example/tests/phpunit.xml.dist
Time: 56 ms, Memory: 2.50Mb
OK (1 test, 1 assertion)
複数のテストケース(クラス)で共有のフィクスチャがある場合に PHPUnit_Framework_TestCase
を継承した抽象クラスを更に継承してテストケースを書くことがありますが、継承元で定義されているメソッドに @before
や @after
などのアノテーションを書いても効果ありません。
<?php
abstract class AbstractBeforeAfter extends PHPUnit_Framework_TestCase
{
/**
* @beforeClass
*/
public static function beforeClass_01()
{
var_dump(__METHOD__);
}
/**
* @before
*/
protected function before_01()
{
var_dump(__METHOD__);
}
/**
* @afterClass
*/
public static function afterClass_01()
{
var_dump(__METHOD__);
}
/**
* @after
*/
protected function after_01()
{
var_dump(__METHOD__);
}
}
class BeforeAfterText extends AbstractBeforeAfter
{
/**
* @test
*/
public function one()
{
$this->assertTrue(true);
}
}
$ phpunit tests/BeforeAfterTest.php
PHPUnit 4.5-dev by Sebastian Bergmann and contributors.
Configuration read from /path/to/example/tests/phpunit.xml.dist
Time: 60 ms, Memory: 2.75Mb
OK (1 test, 1 assertion)
その他の @test
や @dataProvider
などのアノテーションは継承元でも有効なので、なんで? って気もしないでもないですが、次のように使いわけようと思います。
-
setUp/teraDown
- 共通のフィクスチャのために abstract クラスで実装する
- 継承の末端のテストケースでは使わない
-
@before/@after
- テストケースで特有のフィクスチャのために使う
なお、トレイトなら @before/@after
アノテーションも有効です。
<?php
trait BeforeAfterTrait
{
/**
* @beforeClass
*/
public static function beforeClass_01()
{
var_dump(__METHOD__);
}
/**
* @before
*/
protected function before_01()
{
var_dump(__METHOD__);
}
/**
* @afterClass
*/
public static function afterClass_01()
{
var_dump(__METHOD__);
}
/**
* @after
*/
protected function after_01()
{
var_dump(__METHOD__);
}
}
class BeforeAfterTest extends PHPUnit_Framework_TestCase
{
use BeforeAfterTrait;
/**
* @test
*/
public function one()
{
$this->assertTrue(true);
}
}
$ phpunit tests/BeforeAfterTest.php
PHPUnit 4.5-dev by Sebastian Bergmann and contributors.
Configuration read from /path/to/example/tests/phpunit.xml.dist
string(36) "BeforeAfterTraitTest::beforeClass_01"
string(31) "BeforeAfterTraitTest::before_01"
string(30) "BeforeAfterTraitTest::after_01"
string(35) "BeforeAfterTraitTest::afterClass_01"
Time: 64 ms, Memory: 2.50Mb
OK (1 test, 1 assertion)
共通のフィクスチャにはトレイトを使うのもアリかもしれません。
静的メソッドのスタブとモックの廃止
staticExpects
を用いた静的メソッドのテストダブルはなくなりました。
withConsecutive
テストダブルの検証に withConsecutive
が追加されました。詳細は下記の通りです。
以前、次のような記事を書きましたが・・・
withConsecutive
を使えば次のように書けます。
<?php
class Hoge
{
public function func($v) {}
public function xxxx() {}
}
class HogeTest extends PHPUnit_Framework_TestCase
{
public function test()
{
$hoge = $this->getMock('Hoge');
$hoge->expects(exactly(3))->method('func')->withConsecutive(
[identicalTo('A')],
[identicalTo('B')],
[identicalTo('B')]
);
$hoge->func('A');
$hoge->func('B');
$hoge->func('B');
}
}
atLeast と atMost
モックの呼び出し回数のマッチャーに atLeast()
と atMost()
が追加されました。以上と以下です。
<?php
class Hoge
{
public function func() {}
}
class HogeTest extends PHPUnit_Framework_TestCase
{
public function test_ok_atLeast()
{
$hoge = $this->getMock('Hoge');
$hoge->expects($this->atLeast(2))->method('func');
$hoge->func();
$hoge->func();
$hoge->func();
}
public function test_ok_atMost()
{
$hoge = $this->getMock('Hoge');
$hoge->expects($this->atMost(2))->method('func');
$hoge->func();
}
public function test_ng_atLeast()
{
$hoge = $this->getMock('Hoge');
$hoge->expects($this->atLeast(2))->method('func');
$hoge->func();
// Expectation failed for method name is equal to <string:func> when invoked at least 2 times.
// Expected invocation at least 2 times but it occured 1 time(s).
}
public function test_ng_atMost()
{
$hoge = $this->getMock('Hoge');
$hoge->expects($this->atMost(2))->method('func');
$hoge->func();
$hoge->func();
$hoge->func();
// Expectation failed for method name is equal to <string:func> when invoked at most 2 times.
// Expected invocation at most 2 times but it occured 3 time(s).
}
}
Prophecy
まだ alpha ですが PHPUnit 4.5 では Prophecy というモッキングフレームワークが標準で対応されるようです。
Mockery や Phake のほうが良く名前を聞くような気がしないでもないですが、Prophecy だと次のようになります。shouldBeXXX の辺りに phpspec 臭を感じます。
<?php
class HogeClass
{
public function add($a, $b)
{
return $a + $b;
}
}
class MockTest extends PHPUnit_Framework_TestCase
{
/**
* @test
*/
function ok()
{
$obj = $this->prophesize('HogeClass');
$obj->add(1, 2)->willReturn(9)->shouldBeCalled();
$ret = $obj->reveal()->add(1, 2);
assertThat($ret, equalTo(9));
}
/**
* @test
*/
function ng()
{
$obj = $this->prophesize('HogeClass');
$obj->add(1, 2)->willReturn(9)->shouldBeCalled();
// Some predictions failed:
// Double\HogeClass\P1:
// No calls been made that match:
// Double\HogeClass\P1->add(exact(1), exact(2))
// but expected at least one.
}
}
PhpStorm だとマジックメソッドの呼び出しになってる部分 $obj->add
が残念なことになります(Phake でも同じだけど)。
PHPUnit/Framework/Assert/Functions.php
些細な事ですが、Functions.php
のパスが変わったようです。今のところ私は下記のコメントの方法で読むようにしています。
PEAR の PHPUnit 終了のお知らせ
pear.phpunit.de will be shut down on December, 31 2014!
Using the PEAR Installer to install PHPUnit is no longer supported. Please read the documentation and learn how to use PHPUnit from a PHAR or install it via Composer. We are sorry for any inconvenience this may cause.
phpunit の PEAR リポジトリが今年末に終了するとのことです。