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 へ進む
簡単な例として、文字列を処理するクラスを取り上げます。 まず、文字列処理を行う一連の関数の宣言を以下のように作成します。
//---- 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*() 系の関数を使って行い、 テストに合格したかどうかの判断が行われます。
//---- 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 プログラムを実行してください。
//---- stringtest.php ---- require_once 'testcase.php'; require_once 'PHPUnit.php'; $suite = new PHPUnit_TestSuite("StringTest"); $result = PHPUnit::run($suite); echo $result -> toString(); |
コマンドラインで実行すると、以下の出力が得られます。
TestCase stringtest->testtostring() failed: expected true, actual false TestCase stringtest->testcopy() failed: expected , actual Object TestCase stringtest->testadd() failed: expected true, actual false |
ブラウザから実行したい場合は、 $result->toString() を $result->toHTML () へ変更してください。HTML ページが出力されます。
文字列処理クラスの実装を行いましょう。
//---- 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 |
$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 は、実装中に潜むバグを見つけるのに非常に有用なツールなのです。
また、以前に使ったクラスの再実装をするような場合を考えると、 テストスイート無しでは、そのクラスに依存するアプリケーションに 不具合を発生させる可能性が高くなります。 まずテストスイートを作成し、新しいクラスがテストにすべて合格するように保ったまま 再実装を行っていけば、アプリケーションに不具合が起きる事はないでしょう。