PHPUnit は、関数やクラスを自動的にテストする "テストスイート" を作成するためのシンプルなフレームワークを提供します。 PHPUnit は、JUnit からヒントを得ています。 JUnit は、Kent Beck と Erich Gamma が、 eXtreme Programming (XP) 用のツールとして作成したソフトウエアです。XP においては、 小さなソフトウエアコンポーネントを可能な限り早期に頻繁に試験すること、 というルールが定められています。こうすることで、 アプリケーション全体を設定し試験する際になってまで、コンポーネント内部のバグやエラーを 修正する破目にならなくてすみます。ユニットテストと呼ばれるこういったコンポーネント毎の テストは XP の基本原則のひとつですが、だからといって PHPUnit を利用するために XP を実行しないといけない訳ではありません。 PHPUnitは、単体として、クラスや関数をテストする有効なツールであり、 際限のないデバッグ作業を避けるのに有用です。
これまでに行われている良くあるテスト手順は、何らかのクラスを作成した後、 echo() や var_dump() を用いて非体系的にテストを行い、不具合が発生しないことを願う、という流れでしょう。 PHPUnit を使って利益を得るためには、この流れを再考する必要があります。 最善の手順は、以下の通り行うことです。
1. クラス/ API を設計
2. テスト用ツールを作成
3. クラス/ API を実装
4. テスト用ツールを実行
5. 失敗またはエラーを修正し、再度 #4 へ進む
この手順を踏むと時間が多く必要となるように見えますが、その印象は誤りです。 PHPUnit を使ってテストスイートを作成するには数分しかかからず、 テストスイートの実行にも、数秒しかかかりません。
簡単な例として、文字列を処理するクラスを取り上げます。 まず、文字列処理を行う一連の関数の宣言を以下のように作成します。
<?php
//---- string.php ----
class String
{
// 内部データを保持
var $data;
// コンストラクタ
function String($data) {
$this->data = $data;
}
// 文字列オブジェクトのコピーを生成
function copy() {
}
// 文字列をこのオブジェクトに付加
function add($string) {
}
// フォーマット済み文字列を返す
function toString($format) {
}
}
?>
次に、この文字列処理クラスの各関数をテストするテストスイートを作成します。 テストスイートは、 PHPUnit_TestCase を継承した通常の PHP クラスで、このクラス中に 名称が 'test' で始まる "テスト関数" を定義していきます。 テスト関数においては、テスト対象の関数の帰り値と ありうべき正しい値との比較を行います。 この比較は、assert*() 系の関数を使って行い、 テストに合格したかどうかの判断が行われます。
<?php
//---- testcase.php ----
require_once 'string.php';
require_once 'PHPUnit.php';
class StringTest extends PHPUnit_TestCase
{
// 文字列処理クラスのオブジェクト
var $abc;
// このテストスイートのコンストラクタ
function StringTest($name) {
$this->PHPUnit_TestCase($name);
}
// テスト関数が実行される前にコールされる
// この関数は PHPUnit_TestCase にて定義されており、
// ここでオーバーライドしている
function setUp() {
// 新しいインスタンスを文字列'abc'を設定して作成
$this->abc = new String("abc");
}
// テスト関数が実行された後にコールされる
// この関数は PHPUnit_TestCase にて定義されており、
// ここでオーバーライドしている
function tearDown() {
// インスタンスの削除
unset($this->abc);
}
// toString 関数のテスト
function testToString() {
$result = $this->abc->toString('contains %s');
$expected = 'contains abc';
$this->assertTrue($result == $expected);
}
// copy 関数のテスト
function testCopy() {
$abc2 = $this->abc->copy();
$this->assertEquals($abc2, $this->abc);
}
// add 関数のテスト
function testAdd() {
$abc2 = new String('123');
$this->abc->add($abc2);
$result = $this->abc->toString("%s");
$expected = "abc123";
$this->assertTrue($result == $expected);
}
}
?>
それでは、テストを実行してみましょう。 パスが正しいか確認し、この PHP プログラムを実行してください。
<?php
//---- stringtest.php ----
require_once 'testcase.php';
require_once 'PHPUnit.php';
$suite = new PHPUnit_TestSuite("StringTest");
$result = PHPUnit::run($suite);
echo $result -> toString();
?>
コマンドラインで実行すると、以下の出力が得られます。
まだ実装が行われておらず、各文字列処理関数は正しい値を返しませんので、 すべての関数がテストに不合格となります。
ブラウザから実行したい場合は、 $result->toString() を $result->toHTML () へ変更してください。HTML ページが出力されます。
文字列処理クラスの実装を行いましょう。
<?php
//---- string.php ----
class String
{
// 内部データを保持
var $data;
// コンストラクタ
function String($data) {
$this->data = $data;
}
// 文字列オブジェクトのコピーを生成
function copy() {
$ret = new String($this->data);
return $ret;
}
// 文字列をこのオブジェクトに付加
function add($string) {
$this->data = $this->data.$string->toString("%ss");
}
// フォーマット済み文字列を返す
function toString($format) {
$ret = sprintf($format, $this->data);
return $ret;
}
}
?>
実装が終了したら、テストを実行します。
~>php -f stringtest.php
TestCase stringtest->testtostring() passed TestCase stringtest->testcopy() passed TestCase stringtest->testadd() failed: expected true, actual false
あれ、最後のテストが不合格です。タイプミスをしたようです。
string.php
の 16 行目を以下の様に修正します。
<?php
$this->data = $this->data.$string->toString("%s");
?>
そして、テストを再実行します。
~>php -f stringtest.php
TestCase stringtest->testtostring() passed TestCase stringtest->testcopy() passed TestCase stringtest->testadd() passed
すべて合格しました。
たった3つの単純な関数しかないクラスに対しては、大袈裟な手順かも知れません。 しかし、上記は短い例に過ぎないのであって、オンラインショップの ショッピングカートやデータベース抽象化クラスなどの、 大きくて複雑な API を持つクラスを考えて見てください。 PHPUnit は、実装中に潜むバグを見つけるのに非常に有用なツールなのです。
また、以前に使ったクラスの再実装をするような場合を考えると、 テストスイート無しでは、そのクラスに依存するアプリケーションに 不具合を発生させる可能性が高くなります。 まずテストスイートを作成し、新しいクラスがテストにすべて合格するように保ったまま 再実装を行っていけば、アプリケーションに不具合が起きる事はないでしょう。