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

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

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

> [PHPUnit マニュアル – 付録D アップグレード](https://phpunit.de/manual/4.5/ja/appendixes.upgrading.html#appendixes.upgrading.phpunit-4-0 "PHPUnit マニュアル – 付録D アップグレード")
>
> PHPUnit_Framework_TestListener インターフェイスに addRiskyTest() が追加されました。 PHPUnit_Framework_TestListener インターフェイスを実装するクラスは、新たにこのメソッドを実装する必要があります。 たとえば PHPStorm 7 で PHPUnit 4 が使えないのは、これが原因です。 

----

PHPUnit 3.7 から現在の安定版である 4.3 まで、ざっくりとどんな機能が増えているか見てみました。なお、網羅はしていません、気になったものだけです。

## Test Proxies と `will***()`

テストダブルで [Test Proxies](https://github.com/sebastianbergmann/phpunit-mock-objects/issues/132 "Test Proxies") がサポートされました。

これまでは PHPUnit で作成したテストダブルは `will()` で指定しない限りメソッドは null しか返しませんでした。

下記の例のように、モックビルダーで `enableProxyingToOriginalMethods()` を呼んでおくと、テストダブルのメソッド呼び出しで元のオブジェクトのメソッドが呼ばれるようになります。

```php
<?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
<?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](https://github.com/mlively/Phake "Phake") も使うことがあるのですが、Phake なら次のように書けます。

```php
<?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***` の形式のメソッドが使えるようになりました。次の２つは同じ意味です。

```php
$proxy->expects($this->once())
    ->method('doSomethingElse')
    ->willReturn(123);
```

```php
$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
<?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);
    }
}
```

```console
$ 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
<?php
class BeforeAfterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @before
     */
    private function before_01()
    {
        var_dump(__METHOD__);
    }

    /**
     * @test
     */
    public function one()
    {
        $this->assertTrue(true);
    }
}
```

```console
$ 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
<?php
class BeforeAfterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @beforeClass
     */
    public function beforeClass_01()
    {
        var_dump(__METHOD__);
    }

    /**
     * @test
     */
    public function one()
    {
        $this->assertTrue(true);
    }
}
```

```console
$ 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
<?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);
    }
}
```

```console
$ 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
<?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);
    }
}
```

```console
$ 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` が追加されました。詳細は下記の通りです。

- [PHPUnit マニュアル – 第9章 テストダブル](https://phpunit.de/manual/current/ja/test-doubles.html#test-doubles.mock-objects.examples.with-consecutive.php "PHPUnit マニュアル – 第9章 テストダブル")

以前、次のような記事を書きましたが・・・

- [PHPUnit/Phake のモックで、あるメソッドが異なる引数で複数回呼ばれることをテスト - Qiita](http://qiita.com/ngyuki/items/200972aeaa786edbfd43 "PHPUnit/Phake のモックで、あるメソッドが異なる引数で複数回呼ばれることをテスト - Qiita")

`withConsecutive` を使えば次のように書けます。

```php
<?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
<?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](https://github.com/phpspec/prophecy "Prophecy") というモッキングフレームワークが標準で対応されるようです。

[Mockery](https://github.com/padraic/mockery "Mockery") や [Phake](https://github.com/mlively/Phake "Phake") のほうが良く名前を聞くような気がしないでもないですが、Prophecy だと次のようになります。shouldBeXXX の辺りに phpspec 臭を感じます。

```php
<?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` のパスが変わったようです。今のところ私は下記のコメントの方法で読むようにしています。

- [PHPUnitの$this-&gt;assertXXXX() の別記法 - Qiita](http://qiita.com/Hiraku/items/4029f2fd4f852941ec20#comment-91103426cb645b6ecedb "PHPUnitの$this-&gt;assertXXXX() の別記法 - Qiita")

## PEAR の PHPUnit 終了のお知らせ

> [pear.phpunit.de will be shut down on December, 31 2014!](https://phpunit.de/)  
> 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 リポジトリが今年末に終了するとのことです。

- [End of Life for PEAR Installation Method · sebastianbergmann/phpunit Wiki](https://github.com/sebastianbergmann/phpunit/wiki/End-of-Life-for-PEAR-Installation-Method "End of Life for PEAR Installation Method · sebastianbergmann/phpunit Wiki")