チュートリアル

チュートリアル --  PHPUnit 簡易チュートリアル

テストフレームワークの紹介

PHPUnit は、関数やクラスを自動的にテストする "テストスイート" を作成するためのシンプルなフレームワークを提供します。 PHPUnit は、JUnit からヒントを得ています。 JUnit は、Kent Beck と Erich Gamma が、 eXtreme Programming (XP) 用のツールとして作成したソフトウエアです。XP においては、 小さなソフトウエアコンポーネントを可能な限り早期に頻繁に試験すること、 というルールが定められています。こうすることで、 アプリケーション全体を設定し試験する際になってまで、コンポーネント内部のバグやエラーを 修正する破目にならなくてすみます。ユニットテストと呼ばれるこういったコンポーネント毎の テストは XP の基本原則のひとつですが、だからといって PHPUnit を利用するために XP を実行しないといけない訳ではありません。 PHPUnitは、単体として、クラスや関数をテストする有効なツールであり、 際限のないデバッグ作業を避けるのに有用です。

実行手順

これまでに行われている良くあるテスト手順は、何らかのクラスを作成した後、 echo()var_dump() を用いて非体系的にテストを行い、不具合が発生しないことを願う、という流れでしょう。 PHPUnit を使って利益を得るためには、この流れを再考する必要があります。 最善の手順は、以下の通り行うことです。

この手順を踏むと時間が多く必要となるように見えますが、その印象は誤りです。 PHPUnit を使ってテストスイートを作成するには数分しかかからず、 テストスイートの実行にも、数秒しかかかりません。

クラスの設計

簡単な例として、文字列を処理するクラスを取り上げます。 まず、文字列処理を行う一連の関数の宣言を以下のように作成します。
//---- 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
あれ、最後のテストが不合格です。タイプミスをしたようです。 string.php の 16 行目を以下の様に修正します。
$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 は、実装中に潜むバグを見つけるのに非常に有用なツールなのです。

また、以前に使ったクラスの再実装をするような場合を考えると、 テストスイート無しでは、そのクラスに依存するアプリケーションに 不具合を発生させる可能性が高くなります。 まずテストスイートを作成し、新しいクラスがテストにすべて合格するように保ったまま 再実装を行っていけば、アプリケーションに不具合が起きる事はないでしょう。