PHPUnit でエラー時に任意のメッセージをテスト結果に表示させる


PHPUnit でテストが失敗した時に任意のメッセージを表示させたい。あと fail() メソッドで強制的に失敗させた場合など。

しかも php.iniphpunit.xml をいじらず、手軽に。

phpunit エラー時 メッセージ 任意 表示」で Qiita 記事に絞ってググってみたのですが、タイトルからピンポイントでわかる記事がなかったので、自分のググラビリティとして。

TL; DR

すべてのアサーションの最後の引数は、エラー時のメッセージが指定できるようになっています。

assertTrue()の場合の例
assertTrue(bool $condition[, string $message = ''])
class SampleTest extends TestCase
{
    public function testForceFail()
    {
        $result = false;
        $message = '失敗しちゃった。てへ';
        $this->assertTrue($result, $message);
    }
}

アサーションではありませんが、わざと失敗にする、つまり強制的にエラーにする fail() メソッドにもメッセージが指定できます。

fail()メソッド
fail([string $message = ''])
class SampleTest extends TestCase
{
    public function testForceFail()
    {
        if (true) {
            $this->fail('失敗しちゃった。てへ');
        }
    }
}

TS; DR

PHP に限らず、Bash、 Python、 Docker などでコツコツと作った「俺様スクリプト集」があります。いずれも shebang 付きなので、ファイル名を指定するだけで単体コマンドとして動作します。

イメージとしてはこんな感じのコマンド(スクリプト)です。正常終了時は、実行ステータスを "0"、失敗した場合は実行ステータス "1" で終了します。

コマンドの動作例(実装言語は謎)
$ /Regulation/REG-000 'Hello World!'
$ echo $?
0
$ /Regulation/REG-000 'Hell World!'
NG (Bad argument. Must be 'Hello World!'. Given: Hell World!)
$ echo $?
1

こういった、コマンドの動作テストには Bash の Unit Test で有名な batsBash Automated Testing System)を使うのが一般的だと思います。

しかし、なぜか PHPUnit を使いたかったのです。しかも、久しぶりの PHP と PHPUnit だったので色々と忘れてました。

ググってみたのですが、タイトルからピンポイントの記事がありません。それっぽい記事を開いても、「"phpunit.xml" に display_errorsdisplay_startup_errors を入れろ」とあったり、「"php.ini" をいじれ」といった記事が大半でした。

PHP のシンタックス・エラーとかでなく、普通にテスト失敗時にオリジナル(任意)のコメントを表示させたいだけなのです。

「エラー時にメッセージを表示させるだけに、そこまでせんといかんのか」と PHPUnit を使うのを諦めそうになったところ、普通に PHPUnit 公式のドキュメントに記載がありました。そして普通に引数に渡せばよかったのでした・・・

assertTrue()

assertTrue(bool $condition[, string $message = ''])

$conditionfalse の場合にエラー $message を報告します。

実際には、PHPUnit のDocker のコンテナにマウントしていますが、ローカルでもこんな感じです。

ディレクトリ構成
$ tree /Regulation
.
├── REG-000
├── REG-001
├── REG-002
├── REG-003
├── tests
│   ├── REG-000_Test.php
│   ├── REG-001_Test.php
│   ├── REG-002_Test.php
│   ├── REG-003_Test.php
│   ├── autoload.php
│   └── phpunit.xml
└── run_tests.sh
REG-000コマンドの動作例(実装言語は謎)
$ /Regulation/REG-000 'Hello World!'
$ echo $?
0
$ /Regulation/REG-000 'Hell World!'
NG (Bad argument. Must be 'Hello World!'. Given: Hell World!)
$ echo $?
1
run_tests.sh
#!/usr/bin/env bash

phpunit --configuration /Regulation/tests/phpunit.xml /Regulation/tests
REG-000_Test.php(わざとテストを失敗させて動作確認)
<?php
namespace REG\Tests;

class Reg000Test extends TestCase
{
    public function testReg000()
    {
        $message  = 'Hell World!';
        $lastline = exec("/Regulation/REG-000 '${message}'", $output, $return_var);
        $this->assertTrue($return_var === 0, $lastline);
    }
}
phpunit.xml
<?xml version="1.0" encoding="UTF-8" ?>
<phpunit bootstrap="autoload.php">
    <testsuites>
        <testsuite name="Test suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>
autoload.php
<?php
namespace REG\Tests;

// これは Docker phpunit/phpunit の場合の autoload 先ですが、
// 適宜自分の composer の autoload.php のパスを指定してください。
require_once('/tmp/vendor/autoload.php');

class TestCase extends \PHPUnit\Framework\TestCase
{
    // Warning も確実にエラーとして扱うようにする
    public function setUp()
    {
        set_error_handler(function ($errno, $errstr, $errfile, $errline) {
            $msg  = 'Error #' . $errno . ': ';
            $msg .= $errstr . " on line " . $errline . " in file " . $errfile;
            throw new RuntimeException($msg);
        });
    }
    public function tearDown()
    {
        restore_error_handler();
    }
}
テストの実行例(わざとテストを失敗させて、メッセージを確認)
$ # テスト実行
$ cd /Regulation
$ ./run_tests.sh
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.

F                                                                   1 / 1 (100%)

Time: 250 ms, Memory: 4.00MB

There was 1 failure:

1) REG\Tests\Reg000Test::test_Reg000
NG (Bad argument. Must be 'Hello World!'. Given: Hell World!)
Failed asserting that false is true.

/Regulation/tests/REG-000_Test.php:13

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
$ 
$ echo $?
1

一応、正しいテストも。

REG-000_Test.php(今後は正しいテスト)
<?php
namespace REG\Tests;

class Reg001Test extends TestCase
{
    public function testReg000()
    {
        // 正しいデータでのテスト
        $message  = 'Hello World!';
        $lastline = exec("/Regulation/REG-000 '${message}'", $output, $return_var);
        $this->assertTrue($return_var === 0, $lastline);

        // 間違ったデータでのテスト
        $message  = 'Hell World!';
        $lastline = exec("/Regulation/REG-000 '${message}'", $output, $return_var);
        $this->assertTrue($return_var === 1, $lastline);
    }
}
テストの実行(今度はパス)
$ cd /Regulation
$ ./run_tests.sh
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.

.                                                                   1 / 1 (100%)

Time: 215 ms, Memory: 4.00MB

OK (1 test, 2 assertions)
$ 
$ echo $?
0
動作確認済み環境情報
$ # OS(macOS Mojave)
$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.4
BuildVersion:   18E226

$ # PHP バージョン
$ php -v
PHP 7.1.23 (cli) (built: Feb 22 2019 22:08:13) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

$ # PHPUnit バージョン
$ phpunit --version
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.

$ # Composer バージョン
$ composer --version
Composer version 1.7.2 2018-08-16 16:57:12