第5章 Fixtures

テストを記述する際にいちばん時間を食うのは、テストを開始するための事前設定と テスト終了後の後始末の処理を書くことです。この事前設定は、テストの fixture と呼ばれます。

例2.1「PHPUnit を使用した Array および sizeof() のテスト」 では、fixture は $fixture という変数に格納された配列だけでした。 しかしたいていの場合は fixture はこれより複雑なものとなり、 それを準備するにはかなりの量のコードが必要です。本来のテストの内容が、 fixture を設定するためのコードの中に埋もれてしまうことになります。 この問題は、複数のテストで同じような fixture を設定する場合により顕著になります。 テストフレームワークの助けがなければ、 個々のテストのなかで同じような準備コードを繰り返し書くはめになってしまいます。

PHPUnit は、準備用のコードの共有をサポートしています。 各テストメソッドが実行される前に、setUp() という名前のテンプレートメソッドが実行されます。setUp() は、テスト対象のオブジェクトを生成するような処理に使用します。 テストメソッドの実行が終了すると、それが成功したか否かにかかわらず、 tearDown() という名前の別のテンプレートメソッドが実行されます。 tearDown() では、テスト対象のオブジェクトの後始末などを行います。

それでは、setUp() を使用してコードの重複を排除するように 例2.1「PHPUnit を使用した Array および sizeof() のテスト」 を書き換えてみましょう。 まず最初にインスタンス変数 $fixture を宣言し、 メソッド内のローカル変数ではなくこちらを使用するようにします。 そして、array fixture の生成処理を setUp() メソッドに移動します。最後に、 テストメソッド内で重複しているコードを取り除き、 新しく作成したインスタンス変数を使用するようにします。つまり、 assertEquals() で使用しているローカル変数 $fixture を、$this->fixture に置き換えます。

例5.1 setUp() を使用して Array fixture を作成する

<?php
require_once 'PHPUnit2/Framework/TestCase.php';
 
class ArrayTest extends PHPUnit2_Framework_TestCase {
    protected $fixture;
 
    protected function setUp() {
        // Array fixture を作成します。
        $this->fixture = array();
    }
 
    public function testNewArrayIsEmpty() {
        // Array fixture のサイズは 0 のはずです。
        $this->assertEquals(0, sizeof($this->fixture));
    }
 
    public function testArrayContainsAnElement() {
        // Array fixture に要素を追加します。
        $this->fixture[] = 'Element';
 
        // Array fixture のサイズは 1 のはずです。
        $this->assertEquals(1, sizeof($this->fixture));
    }
}
?>


各テストメソッドが実行されるたびに、setUp() および tearDown() が一度ずつコールされます。 「テストケース内の全テストメソッドについて一度だけコールするように したほうがよいのではないか」とお考えになるかもしれませんが、 そのようにすると各テストを互いに独立した状態にすることが難しくなります。

テストメソッドごとに setUp() および tearDown() が一度ずつ実行されるだけでなく、 テストメソッドごとに、新しいテストケースクラスのインスタンスが作成されます (13章PHPUnit の実装 を参照ください)。

tearDown() よりも setUp()

setUp()tearDown() は理屈上では対称的になるはずですが、実際にはそうではありません。実際には、 tearDown() を実装する必要があるのは setUp() で外部リソース (ファイルやソケットなど) を割り当てた場合のみです。もし setUp() で単に PHP オブジェクトを作成しただけの場合は、 一般には tearDown() は必要ありません。しかし、もし setUp() で大量のオブジェクトを作成した場合には、 それらの後始末をするために tearDown() で変数を unset() したくなることもあるでしょう。 テストケースオブジェクト自体のガベージコレクションにはあまり意味がありません。

バリエーション

ふたつのテストがあって、それぞれの setup がほんの少しだけ違う場合にはどうなるでしょう? このような場合は、二種類の可能性が考えられます。

  • もし setUp() の違いがごくわずかなものなら、 その違う部分を setUp() からテストメソッドのほうに移動させます。

  • setUp() の違いが大きければ、 テストケースクラスを別に分ける必要があります。それぞれのクラスには、 setup の違いを表す名前をつけます。

スイートレベルの設定

PHPUnit には、スイートレベルでの設定をするための便利な方法はありません。 複数のテストの間で fixture を共有したいなんてことは、通常はめったにないはずです。 しかし、設計上の問題などでどうしても fixture を共有しなければならないこともあるでしょう。

複数のテスト間で共有する意味のある fixture の例として意味のあるものといえば、 データベースとの接続でしょう。テストのたびに新しいデータベース接続を毎回作成するのではなく、 最初にログインした状態を再利用するということです。こうすることで、 テストの実行時間を短縮できます。これを行うには、データベースに関するテストを DatabaseTests という名前のクラスに書き、デコレータ (decorator) オブジェクト TestSetup でテストスイートをラップします。オーバーライドした setUp() でデータベース接続をオープンし、 tearDown() で接続を閉じるようにします。この例を 例5.2「スイートレベル設定のデコレータを書く」 に示します。DatabaseTestSetup デコレータを起動することで、 DatabaseTests のテストを行うことができます。例えば、 PHPUnit のコマンドライン版テストランナーでは phpunit DatabaseTestSetup とします。

例5.2 スイートレベル設定のデコレータを書く

<?php
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/Extensions/TestSetup.php';
 
class DatabaseTestSetup extends PHPUnit2_Extensions_TestSetup {
    protected $connection = NULL;
 
    protected function setUp() {
        $this->connection = new PDO(
          'mysql:host=wopr;dbname=test',
          'root',
          ''
        );
    }
 
    protected function tearDown() {
        $this->connection = NULL;
    }
 
    public static function suite() {
        return new DatabaseTestSetup(
          new PHPUnit2_Framework_TestSuite('DatabaseTests')
        );
    }
}
?>


このように fixture を共有することがテストの価値を下げてしまうということを、 まだうまく伝え切れていないかもしれません。問題なのは、 オブジェクト間の連携が密になってしまっているという設計なのです。 複数が連携しているようなテストを作って設計上の問題から目をそらしてしまうのではなく、 きちんと設計しなおした上で、スタブ (10章スタブ を参照ください) を使用するテストを書くことをお勧めします。