読者です 読者をやめる 読者になる 読者になる

はむはむエンジニアぶろぐ

このブログのコンセプトは"ハッキングの為なら愛する家族を傷つけることをいとわない" 自分にとってエンジニアリングは "手段ではなく生きる目的" である

PHPUnitの便利な機能を紹介する

PHP PHPUnit

この記事はPHP Advent Calendar 2015 - Qiita 14日目の記事です。
担当は@secret_hamuhamu です。

みなさんは、PHPのテスティングツールに何をお使いでしょうか?
Behat?PHPSpec?PHPUnit?
今日は、PHPUnitの便利な機能をいくつか紹介したいと思います。

なお、PHPUnitのバージョンは2015年12月14日現在時点で、最新安定版の 5.1 とします。

echoやprintなどの出力をテストする

echoやprintといった出力を伴うものをテストすることが出来ます。

<?php
class Hoge
{
    public function output()
    {
        echo 'hoge';
    }

    public function output2()
    {
        echo 'hoXX';
    }
}

class HogeTest extends PHPUnit_Framework_TestCase
{
    /** @test */
    public function hogeが出力されること()
    {
        $this->expectOutputString('hoge');

        $hoge = new Hoge();
        $hoge->output();
   }

    /** @test */
    public function hoから始まる文字列が出力されること()
    {
        $this->expectOutputRegex('/\Aho.*/');

        $hoge = new Hoge();
        $hoge->output2();
    }
}

Expectedを、expectOutputString / expectOutputRegexの引数に渡し、Actualをその後に出力させます。
クラスの中で出力させることは、あまりないと思うが、出来合いクラスが出力をする振る舞いをしていて、壊さずにリファクタリングする際は便利だと思う。


特定のグループに属するテストを実行する

このように、とある変更がたくさんのエラーを引き起こしてしまっている場合、うんざりしませんか?
f:id:secret_hamuhamu:20151211185818p:plain

redからgreenまでの道のりが遠く、エラーメッセージも大量に出力されるため疲れてしまいます。


そんなときは、 @group を使います。
手におえるサイズにしてリファクタリングしていきましょう。

このようにテストメソッドのアノテーションにグループ名をつけます。

<?php
class HogeTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     * @group current
     */
    public function Trueが返ること()
    {
        $hoge = new Hoge();

        $this->assertTrue($hoge->is('arg'));
    }


するとPHPUnitで、group名を指定して、該当グループのみ実行することが出来ます。
f:id:secret_hamuhamu:20151211190141p:plain

これなら、リファクタリングの心理的ハードルが下がると思います。
エラーがたくさんあって絶望していても、少しずつ確実に進むために活用をするといいと思う。


最初にエラー又は失敗時にテスト実行を止める

先ほどの、 @group より簡単なのが、 --stop-on-failure です。
最初にエラー又は失敗時にテスト実行を止めてくれます。
とりあえず、最初に失敗しているテストを修正したい時に使うといいです。
f:id:secret_hamuhamu:20151211190310p:plain

stop-on-failure のほうが @group よりお手軽ですが、 @group のよいところは、意味のある単位でテスト実行できることです。


未実装であることをマーキングする

テストファーストでテストを書くと当然、実装よりも先にテストが出来上がります。

<?php
class Hoge
{
    public function newInterface()
    {
        // 実装は空
    }

    public function newInterface2()
    {
        // 実装は空
    }
}

class HogeTest extends PHPUnit_Framework_TestCase
{
    /** @test */
    public function 〇〇できること()
    {
        $hoge = new Hoge();

        $this->assertSame('expected', $hoge->newInterface());
    }

    public function XXできること()
    {
        $hoge = new Hoge();

        $this->assertSame('expected', $hoge->newInterface2());
    }
}

当然、テストは失敗します。
すぐに、実装を行うのであれば問題ありません。
ですが、今すぐではなく、近い将来に実装するため機能の洗い出しも兼ねてテストを作成した時に、このテストって邪魔になります。


実装がされていない空のテストを除外してテストをすることも出来ますが、「未実装です」ということを明示した方がよいと思います。
未実装であるということをマーク付するには markTestIncomplete を使います。

<?php
class HogeTest extends PHPUnit_Framework_TestCase
{
    /** @test */
    public function 〇〇できること()
    {
        $this->markTestIncomplete(
            'まだ実装されていません。'
        );

        $hoge = new Hoge();

        $this->assertSame('expected', $hoge->newInterface());
    }

f:id:secret_hamuhamu:20151211190418p:plain
Iという未実装(Incomplete) のマーキングがされました。