テストを書きやすくする、あるいはテストの実行結果の表示方法を変更するなど、 PHPUnit はさまざまな方法で拡張することができます。 PHPUnit を拡張するための第一歩をここで説明します。
PHPUnit_Framework_TestCase
を継承した抽象サブクラスにカスタムアサーションやユーティリティメソッドを書き、
そのクラスをさらに継承してテストクラスを作成します。
これが、PHPUnit を拡張するための一番簡単な方法です。
カスタムアサーションを作成するときには、PHPUnit 自体のアサーションの実装方法を真似るのがおすすめです。
例 15.1 を見ればわかるとおり、
assertTrue()
メソッドは
isTrue()
および assertThat()
メソッドの単なるラッパーに過ぎません。
isTrue()
が matcher オブジェクトを作り、それを
assertThat()
に渡して評価しています。
例 15.1: PHPUnit_Framework_Assert クラスの assertTrue() および isTrue() メソッド
<?php abstract class PHPUnit_Framework_Assert { // ... /** * Asserts that a condition is true. * * @param boolean $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertTrue($condition, $message = '') { self::assertThat($condition, self::isTrue(), $message); } // ... /** * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object. * * @return PHPUnit_Framework_Constraint_IsTrue * @since Method available since Release 3.3.0 */ public static function isTrue() { return new PHPUnit_Framework_Constraint_IsTrue; } // ... }?>
例 15.2 は、
PHPUnit_Framework_Constraint_IsTrue
が
matcher オブジェクト (あるいは制約) のために抽象クラス
PHPUnit_Framework_Constraint
を継承している部分です。
例 15.2: PHPUnit_Framework_Constraint_IsTrue クラス
<?php class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns TRUE if the * constraint is met, FALSE otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ public function matches($other) { return $other === TRUE; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is true'; } }?>
assertTrue()
や
isTrue()
メソッドの実装を
PHPUnit_Framework_Constraint_IsTrue
クラスと同じようにしておけば、
アサーションの評価やタスクの記録 (テストの統計情報に自動的に更新するなど)
を assertThat()
が自動的に行ってくれるようになります。
さらに、モックオブジェクトを設定する際の matcher として isTrue()
メソッドを使えるようにもなります。
例 15.3 は、
PHPUnit_Framework_TestListener
インターフェイスのシンプルな実装例です。
例 15.3: シンプルなテストリスナー
<?php class SimpleTestListener implements PHPUnit_Framework_TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' の実行中にエラーが発生\n", $test->getName()); } public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { printf("テスト '%s' に失敗\n", $test->getName()); } public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' は未完成\n", $test->getName()); } public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' は危険\n", $test->getName()); } public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' をスキップ\n", $test->getName()); } public function startTest(PHPUnit_Framework_Test $test) { printf("テスト '%s' が開始\n", $test->getName()); } public function endTest(PHPUnit_Framework_Test $test, $time) { printf("テスト '%s' が終了\n", $test->getName()); } public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("テストスイート '%s' が開始\n", $suite->getName()); } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("テストスイート '%s' が終了\n", $suite->getName()); } } ?>
例 15.4
は、抽象クラス PHPUnit_Framework_BaseTestListener
のサブクラスを作る例です。これは、インターフェイスのメソッドのうち実際に使うものだけを指定し、
他のメソッドについては空の実装を提供します。
例 15.4: ベーステストリスナーの利用法
<?php class ShortTestListener extends PHPUnit_Framework_BaseTestListener { public function endTest(PHPUnit_Framework_Test $test, $time) { printf("テスト '%s' が終了\n", $test->getName()); } } ?>
「テストリスナー」 に、自作のテストリスナーをテスト実行時にアタッチするための PHPUnit の設定方法についての説明があります。
PHPUnit_Extensions_TestDecorator
のサブクラスでテストケースあるいはテストスイートをラッピングし、
デコレータパターンを使用することで
各テストの実行前後に何らかの処理をさせることができます。
PHPUnit には、PHPUnit_Extensions_RepeatedTest
および PHPUnit_Extensions_TestSetup
という 2 つの具象テストデコレータが付属しています。
前者はテストを繰り返し実行し、それらが全て成功した場合にのみ成功とみなします。
後者については 第 4 章 で説明しました。
例 15.5
は、テストデコレータ PHPUnit_Extensions_RepeatedTest
の一部を抜粋したものです。独自のデコレータを作成するための参考にしてください。
例 15.5: RepeatedTest デコレータ
<?php require_once 'PHPUnit/Extensions/TestDecorator.php'; class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator { private $timesRepeat = 1; public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1) { parent::__construct($test); if (is_integer($timesRepeat) && $timesRepeat >= 0) { $this->timesRepeat = $timesRepeat; } } public function count() { return $this->timesRepeat * $this->test->count(); } public function run(PHPUnit_Framework_TestResult $result = NULL) { if ($result === NULL) { $result = $this->createResult(); } for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) { $this->test->run($result); } return $result; } } ?>
PHPUnit_Framework_Test
インターフェイスの機能は限られており、
実装するのは簡単です。PHPUnit_Framework_Test
を実装するのは PHPUnit_Framework_TestCase
の実装より単純で、
これを用いて例えば データ駆動のテスト (data-driven tests)
などを実行します。
カンマ区切り (CSV) ファイルの値と比較する、データ駆動のテストを
例 15.6
に示します。このファイルの各行は foo;bar
のような形式になっており (訳注: CSV じゃない……)、
最初の値が期待値で 2 番目の値が実際の値です。
例 15.6: データ駆動のテスト
<?php class DataDrivenTest implements PHPUnit_Framework_Test { private $lines; public function __construct($dataFile) { $this->lines = file($dataFile); } public function count() { return 1; } public function run(PHPUnit_Framework_TestResult $result = NULL) { if ($result === NULL) { $result = new PHPUnit_Framework_TestResult; } foreach ($this->lines as $line) { $result->startTest($this); PHP_Timer::start(); $stopTime = NULL; list($expected, $actual) = explode(';', $line); try { PHPUnit_Framework_Assert::assertEquals( trim($expected), trim($actual) ); } catch (PHPUnit_Framework_AssertionFailedError $e) { $stopTime = PHP_Timer::stop(); $result->addFailure($this, $e, $stopTime); } catch (Exception $e) { $stopTime = PHP_Timer::stop(); $result->addError($this, $e, $stopTime); } if ($stopTime === NULL) { $stopTime = PHP_Timer::stop(); } $result->endTest($this, $stopTime); } return $result; } } $test = new DataDrivenTest('data_file.csv'); $result = PHPUnit_TextUI_TestRunner::run($test); ?>
PHPUnit 4.2.0 by Sebastian Bergmann. .F Time: 0 seconds There was 1 failure: 1) DataDrivenTest Failed asserting that two strings are equal. expected string <bar> difference < x> got string <baz> /home/sb/DataDrivenTest.php:32 /home/sb/DataDrivenTest.php:53 FAILURES! Tests: 2, Failures: 1.