第9章 不完全なテスト・テストの省略

不完全なテスト

新しいテストケースクラスを作成する際には、 これから書くべきテストの内容をはっきりさせるために、 まず最初は以下のような空のテストメソッドを書きたくなることでしょう。

public function testSomething()
{
}

しかし、PHPUnit フレームワークでは空のメソッドを「成功した」 と判断してしまうという問題があります。このような解釈ミスがあると、 テスト結果のレポートが無意味になってしまいます。 そのテストがほんとうに成功したのか、 それともまだテストが実装されていないのかが判断できないからです。 実装していないテストメソッドの中で $this->fail() をコールするようにしたところで事態は何も変わりません。 こうすると、テストが「失敗した」と判断されてしまいます。 これは未実装のテストが「成功」と判断されてしまうのと同じくらいまずいことです (訳注: レポートを見ても、そのテストがほんとうに失敗したのか、 まだ実装されていないだけなのかがわかりません)。

テストの成功を青信号、失敗を赤信号と考えるなら、 テストが未完成あるいは未実装であることを表すための黄信号が必要です。 そのような場合に使用するインターフェイスが PHPUnit_Framework_IncompleteTest で、 これは未完成あるいは未実装のテストメソッドで発生する例外を表すものです。 このインターフェイスの標準的な実装が PHPUnit_Framework_IncompleteTestError です。

例 9.1 では SampleTest というテストケースクラスを定義しています。 便利なメソッド markTestIncomplete() (これは、自動的に PHPUnit_Framework_IncompleteTestError を発生させます) をテストメソッド内でコールすることで、 このメソッドがまだ完成していないことをはっきりさせます。

例 9.1: テストに未完成の印をつける

<?php
class SampleTest extends PHPUnit_Framework_TestCase
{
    public function testSomething()
    {
        // オプション: お望みなら、ここで何かのテストをしてください。
        $this->assertTrue(TRUE, 'これは動いているはずです。');

        // ここで処理を止め、テストが未完成であるという印をつけます。
        $this->markTestIncomplete(
          'このテストは、まだ実装されていません。'
        );
    }
}
?>


未完成のテストは、PHPUnit のコマンドライン版テストランナーでは以下のように I で表されます。

phpunit --verbose SampleTest
PHPUnit 3.7.0 by Sebastian Bergmann.

I

Time: 0 seconds, Memory: 3.75Mb

There was 1 incomplete test:

1) SampleTest::testSomething
このテストは、まだ実装されていません。

/home/sb/SampleTest.php:12
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 1, Incomplete: 1.

表 9.1 に、テストを未完成扱いにするための API を示します。

表9.1 未完成のテスト用の API

メソッド意味
void markTestIncomplete()現在のテストを未完成扱いにします。
void markTestIncomplete(string $message)現在のテストを未完成扱いにします。それを説明する文字列として $message を使用します。


テストの省略

すべてのテストがあらゆる環境で実行できるわけではありません。 考えてみましょう。たとえば、データベースの抽象化レイヤーを使用しており、 それがさまざまなドライバを使用してさまざまなデータベースシステムを サポートしているとします。MySQL ドライバのテストができるのは、 当然 MySQL サーバが使用できる環境だけです。

例 9.2 に示すテストケースクラス DatabaseTest には、 テストメソッド testConnection() が含まれています。 このクラスのテンプレートメソッド setUp() では、 MySQLi 拡張モジュールが使用可能かを調べたうえで、もし使用できない場合は markTestSkipped() メソッドでテストを省略するようにしています。

例 9.2: テストを省略する

<?php
class DatabaseTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        if (!extension_loaded('mysqli')) {
            $this->markTestSkipped(
              'MySQLi 拡張モジュールが使用できません。'
            );
        }
    }

    public function testConnection()
    {
        // ...
    }
}
?>


飛ばされたテストは、PHPUnit のコマンドライン版テストランナーでは以下のように S で表されます。

phpunit --verbose DatabaseTest
PHPUnit 3.7.0 by Sebastian Bergmann.

S

Time: 0 seconds, Memory: 3.75Mb

There was 1 skipped test:

1) DatabaseTest::testConnection
MySQLi 拡張モジュールが使用できません。

/home/sb/DatabaseTest.php:9
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 0, Skipped: 1.

表 9.2 に、テストを省略するための API を示します。

表9.2 テストを省略するための API

メソッド意味
void markTestSkipped()現在のテストを省略扱いにします。
void markTestSkipped(string $message)現在のテストを省略扱いにします。それを説明する文字列として $message を使用します。


@requires によるテストのスキップ

ここまでに示したメソッドに加えて、 @requires アノテーションを使って共通の事前条件を記述することもできます。

表9.3 @requires の例用例

取り得る値別の例
PHPPHP のバージョン@requires PHP 5.3.3@requires PHP 5.4-dev
PHPUnitPHPUnit のバージョン@requires PHPUnit 3.6.3@requires PHPUnit 3.7
functionfunction_exists に渡せるパラメータ@requires function imap_open@requires function ReflectionMethod::setAccessible
extension拡張モジュール名@requires extension mysqli@requires extension curl


例 9.3: @requires を使ったテストケースのスキップ

<?php
/**
 * @requires extension mysqli
 */
class DatabaseTest extends PHPUnit_Framework_TestCase
{
    /**
     * @requires PHP 5.3
     */
    public function testConnection()
    {
        // このテストには mysqli 拡張モジュールと PHP 5.3 以降が必須です
    }

    // ... その他のすべてのテストには mysqli 拡張モジュールが必須です
}
?>


特定のバージョンの PHP でしか使えない構文を利用する場合は、 「テストスイート」 にあるように XML 設定ファイルでのバージョン依存のインクルードを検討しましょう。

このページの改善案をGitHubで提案してください!