第15章 PHPUnit の拡張

テストを書きやすくする、あるいはテストの実行結果の表示方法を変更するなど、 PHPUnit はさまざまな方法で拡張することができます。 PHPUnit を拡張するための第一歩をここで説明します。

PHPUnit2_Framework_TestCase のサブクラスの作成

PHPUnit2_Framework_TestCase を継承した抽象サブクラスにユーティリティメソッドを書き、 そのクラスをさらに継承してテストクラスを作成します。 これが、PHPUnit を拡張するための一番簡単な方法です。

アサートクラスの作成

あなたの目的に合った独自のアサーションを実装したクラスを作成します。

PHPUnit2_Extensions_TestDecorator のサブクラスの作成

PHPUnit2_Extensions_TestDecorator のサブクラスでテストケースあるいはテストスイートをラッピングし、 デコレータパターンを使用することで 各テストの実行前後に何らかの処理をさせることができます。

PHPUnit には、PHPUnit2_Extensions_RepeatedTest および PHPUnit2_Extensions_TestSetup という 2 つの具象テストデコレータが付属しています。 前者はテストを繰り返し実行し、それらが全て成功した場合にのみ成功とみなします。 後者については 5章Fixtures で説明しました。

例15.1「RepeatedTest デコレータ」 は、テストデコレータ PHPUnit2_Extensions_RepeatedTest の一部を抜粋したものです。独自のデコレータを作成するための参考にしてください。

例15.1 RepeatedTest デコレータ

<?php
require_once 'PHPUnit2/Extensions/TestDecorator.php';
 
class PHPUnit2_Extensions_RepeatedTest extends PHPUnit2_Extensions_TestDecorator {
    private $timesRepeat = 1;
 
    public function __construct(PHPUnit2_Framework_Test $test, $timesRepeat = 1) {
        parent::__construct($test);
 
        if (is_integer($timesRepeat) &&
            $timesRepeat >= 0) {
            $this->timesRepeat = $timesRepeat;
        }
    }
 
    public function countTestCases() {
        return $this->timesRepeat * $this->test->countTestCases();
    }
 
    public function run($result = NULL) {
        if ($result === NULL) {
            $result = $this->createResult();
        }
 
        for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) {
            $this->test->run($result);
        }
 
        return $result;
    }
}
?>


PHPUnit2_Framework_Test を実装する

PHPUnit2_Framework_Test インターフェイスの機能は限られており、 実装するのは簡単です。PHPUnit2_Framework_Test を実装するのは PHPUnit2_Framework_TestCase の実装より単純で、 これを用いて例えば データ駆動のテスト (data-driven tests) などを実行します。

カンマ区切り (CSV) ファイルの値と比較する、データ駆動のテストを 例15.2「データ駆動のテスト」 に示します。このファイルの各行は foo;bar のような形式になっており (訳注: CSV じゃない……)、 最初の値が期待値で 2 番目の値が実際の値です。

例15.2 データ駆動のテスト

<?php
require_once 'PHPUnit2/Framework/Assert.php';
require_once 'PHPUnit2/Framework/Test.php';
require_once 'PHPUnit2/Framework/TestResult.php';
 
class DataDrivenTest implements PHPUnit2_Framework_Test {
    private $lines;
 
    public function __construct($dataFile) {
        $this->lines = file($dataFile);
    }
 
    public function countTestCases() {
        return sizeof($this->lines);
    }
 
    public function run($result = NULL) {
        if ($result === NULL) {
            $result = new PHPUnit2_Framework_TestResult;
        }
 
        $result->startTest($this);
 
        foreach ($this->lines as $line) {
            list($expected, $actual) = explode(';', $line);
 
            try {
                PHPUnit2_Framework_Assert::assertEquals(trim($expected), trim($actual));
            }
 
            catch (PHPUnit2_Framework_ComparisonFailure $e) {
                $result->addFailure($this, $e);
            }
 
            catch (Exception $e) {
                $result->addError($this, $e);
            }
        }
 
        $result->endTest($this);
 
        return $result;
    }
}
 
$test   = new DataDrivenTest('data_file.csv');
$result = $test->run();
 
$failures = $result->failures();
print $failures[0]->thrownException()->toString();
?>
expected: <foo> but was: <bar>


PHPUnit2_Framework_TestResult のサブクラスの作成

独自の PHPUnit2_Framework_TestResult オブジェクトを run() メソッドに渡すと、 テストの実行方法や収集されるテスト結果を変更することができます。

PHPUnit2_Framework_TestListener の実装

テスト結果をカスタマイズするために、必ず PHPUnit2_Framework_TestResult のサブクラスを書かなければならないというわけではありません。たいていは、 新しい PHPUnit2_Framework_TestListener を実装して (表14.10「TestListener のコールバック」 を参照ください)、 テストの前にそれを PHPUnit2_Framework_TestResult オブジェクトにアタッチするだけで十分です。

例15.3「シンプルなテストリスナー」 は、PHPUnit2_Framework_TestListener インターフェイスを実装する単純な例です。

例15.3 シンプルなテストリスナー

<?php
require_once 'PHPUnit2/Framework/TestListener.php';
 
class SimpleTestListener
implements PHPUnit2_Framework_TestListener {
  public function
  addError(PHPUnit2_Framework_Test $test, Exception $e) {
    printf(
      "テスト '%s' の実行中にエラーが発生しました。\n",
      $test->getName()
    );
  }
 
  public function
  addFailure(PHPUnit2_Framework_Test $test,
             PHPUnit2_Framework_AssertionFailedError $e) {
    printf(
      "テスト '%s' に失敗しました。\n",
      $test->getName()
    );
  }
 
  public function
  addIncompleteTest(PHPUnit2_Framework_Test $test,
                    Exception $e) {
    printf(
      "テスト '%s' は未完了です。\n",
      $test->getName()
    );
  }
 
  public function startTest(PHPUnit2_Framework_Test $test) {
    printf(
      "テスト '%s' が開始されました。\n",
      $test->getName()
    );
  }
 
  public function endTest(PHPUnit2_Framework_Test $test) {
    printf(
      "テスト '%s' が終了しました。\n",
      $test->getName()
    );
  }
 
  public function
  startTestSuite(PHPUnit2_Framework_TestSuite $suite) {
    printf(
      "テストスイート '%s' が開始されました。\n",
      $suite->getName()
    );
  }
 
  public function
  endTestSuite(PHPUnit2_Framework_TestSuite $suite) {
    printf(
      "テストスイート '%s' が終了しました。\n",
      $suite->getName()
    );
  }
}
?>


例15.4「テストスイートの実行と監視」 は、テストスイートを実行して監視する方法を示したものです。

例15.4 テストスイートの実行と監視

<?php
require_once 'PHPUnit2/Framework/TestResult.php';
require_once 'PHPUnit2/Framework/TestSuite.php';
 
require_once 'ArrayTest.php';
require_once 'SimpleTestListener.php';
 
// ArrayTest クラスのテストを含む
// テストスイートを作成します。
$suite = new PHPUnit2_Framework_TestSuite('ArrayTest');
 
// テスト結果オブジェクトを作成し、そこにオブザーバとして
// SimpleTestListener をアタッチします。
$result = new PHPUnit2_Framework_TestResult;
$result->addListener(new SimpleTestListener);
 
// テストを実行します。
$suite->run($result);
?>
テストスイート 'ArrayTest' が開始されました。
テスト 'testNewArrayIsEmpty' が開始されました。
テスト 'testNewArrayIsEmpty' が終了しました。
テスト 'testArrayContainsAnElement' が開始されました。
テスト 'testArrayContainsAnElement' が終了しました。
テストスイート 'ArrayTest' が終了しました。


新しいテストランナーの作成

テストの実行結果を異なる方法で受け取りたい場合には、 独自のテストランナーを作成します。その際の初めの一歩となるのが、 PHPUnit2_TextUI_TestRunner クラス (PHPUnit のコマンドライン版テストランナー) の親クラスである抽象クラス PHPUnit2_Runner_BaseTestRunner です。