34
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPAdvent Calendar 2014

Day 13

PHPUnit 3.7~4.3 で追加された機能

Posted at

PhpStorm 7 が PHPUnit 4 をサポートしていなかったので少し前まで PHPUnit は 3.7 を使っていましたが、9 月に PhpStorm 8 がリリースされ、PHPUnit 4 も動作するようになったので、そろそろ使っていこうかと思います。

なお、PhpStorm 7 で PHPUnit 4 が使えなかったのは次の理由だそうです。

PHPUnit マニュアル – 付録D アップグレード

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@afterprotected 以上の可視性が必要です。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@afterClasspublic 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 というモッキングフレームワークが標準で対応されるようです。

MockeryPhake のほうが良く名前を聞くような気がしないでもないですが、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 リポジトリが今年末に終了するとのことです。

34
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?