Prev Next

第20章 PHPUnit の拡張

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

PHPUnit_Framework_TestCase のサブクラスの作成

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

カスタムアサーションの作成

カスタムアサーションを作成するときには、PHPUnit 自体のアサーションの実装方法を真似るのがおすすめです。 例 20.1 を見ればわかるとおり、 assertTrue() メソッドは isTrue() および assertThat() メソッドの単なるラッパーに過ぎません。 isTrue() が matcher オブジェクトを作り、それを assertThat() に渡して評価しています。

例 20.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;
}

// ...
}?>

例 20.2 は、 PHPUnit_Framework_Constraint_IsTrue が matcher オブジェクト (あるいは制約) のために抽象クラス PHPUnit_Framework_Constraint を継承している部分です。

例 20.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 evaluate($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() メソッドを使えるようにもなります。

PHPUnit_Framework_TestListener の実装

例 20.3 は、 PHPUnit_Framework_TestListener インターフェイスのシンプルな実装例です。

例 20.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 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());
}
}
?>

「テストリスナー」 に、自作のテストリスナーをテスト実行時にアタッチするための PHPUnit の設定方法についての説明があります。

PHPUnit_Extensions_TestDecorator のサブクラスの作成

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

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

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

例 20.4: 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_Test を実装するのは PHPUnit_Framework_TestCase の実装より単純で、 これを用いて例えば データ駆動のテスト (data-driven tests) などを実行します。

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

例 20.5: データ駆動のテスト

<?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();

list($expected, $actual) = explode(';', $line);

try {
PHPUnit_Framework_Assert::assertEquals(
trim($expected), trim($actual)
);
}

catch (PHPUnit_Framework_AssertionFailedError $e) {
$result->addFailure($this, $e, PHP_Timer::stop());
}

catch (Exception $e) {
$result->addError($this, $e, PHP_Timer::stop()());
}

$result->endTest($this, PHP_Timer::stop());
}

return $result;
}
}

$test = new DataDrivenTest('data_file.csv');
$result = PHPUnit_TextUI_TestRunner::run($test);
?>
PHPUnit 3.5.13 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.

Prev Next
1. 自動テスト
2. PHPUnit の目標
3. PHPUnit のインストール
4. PHPUnit 用のテストの書き方
テストの依存性
データプロバイダ
例外のテスト
PHP のエラーのテスト
アサーション
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
5. コマンドラインのテストランナー
6. Fixtures
tearDown() よりも setUp()
バリエーション
Fixture の共有
グローバルな状態
7. テストの構成
ファイルシステムを用いたテストスイートの構成
XML 設定ファイルを用いたテストスイートの構成
8. テストケースの拡張
出力内容のテスト
9. データベースのテスト
データベースのテストに対応しているベンダー
データベースのテストの難しさ
データベーステストの四段階
1. データベースのクリーンアップ
2. フィクスチャの準備
3–5. テストの実行、結果の検証、そして後始末
PHPUnit のデータベーステストケースの設定
getConnection() の実装
getDataSet() の実装
データベーススキーマ (DDL) とは?
ヒント: 自前でのデータベーステストケースの抽象化
データセットとデータテーブルについて知る
利用できる実装
外部キーには注意
自作のデータセットやデータテーブルの実装
接続 API
データベースアサーション API
テーブルの行数のアサーション
テーブルの状態のアサーション
クエリの結果のアサーション
複数のテーブルの状態のアサーション
よくある質問
PHPUnit は、テストごとにデータベーススキーマを作り直すの?
PDO を使ったアプリケーションじゃないと Database Extension を使えないの?
Too much Connections というエラーが出たらどうすればいい?
フラット XML や CSV のデータセットで NULL を扱う方法は?
10. 不完全なテスト・テストの省略
不完全なテスト
テストの省略
11. テストダブル
スタブ
モックオブジェクト
ウェブサービスのスタブおよびモック
ファイルシステムのモック
12. テストの進め方
開発中のテスト
デバッグ中のテスト
13. テスト駆動開発
銀行口座の例
14. 振舞駆動開発
ボウリングゲームの例
15. コードカバレッジ解析
カバーするメソッドの指定
コードブロックの無視
ファイルのインクルードや除外
16. テストのその他の使用法
アジャイルな文書作成
複数チームでのテスト
17. 雛形ジェネレータ
テストケースクラスの雛形の作成
テストケースクラスからのクラスの雛形の作成
18. PHPUnit と Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
19. ログ出力
テスト結果 (XML)
テスト結果 (TAP)
テスト結果 (JSON)
コードカバレッジ (XML)
20. PHPUnit の拡張
PHPUnit_Framework_TestCase のサブクラスの作成
カスタムアサーションの作成
PHPUnit_Framework_TestListener の実装
PHPUnit_Extensions_TestDecorator のサブクラスの作成
PHPUnit_Framework_Test の実装
A. アサーション
B. アノテーション
@assert
@author
@backupGlobals
@backupStaticAttributes
@covers
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@outputBuffering
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. XML 設定ファイル
PHPUnit
テストスイート
グループ
コードカバレッジ対象のファイルの追加や除外
ログ出力
テストリスナー
PHP INI 項目や定数、グローバル変数の設定
Selenium RC の設定ブラウザ
D. 目次
E. 参考文献
F. 著作権