ブログ

ryuzeeによるブログ記事。不定期更新
アジャイル開発に取り組むチーム向けのコーチングや、技術顧問、認定スクラムマスター研修などの各種トレーニングを提供しています。ぜひお気軽にご相談ください(初回相談無料)

自動テストのfixtureを効率的に管理する方法

みなさんこんにちは。@ryuzeeです。

僕がやっている案件(PHP)はもともとテストコードのないレガシーなプロジェクトで、それを改善するためにずっと動作を確認するための結合レベルの自動テストを増やしてきました。 そんな中で、僕のところではどうやってテスト用のfixtureを管理しているか事例として紹介したいと思います。

最初にコアとなるfixtureを用意する

みんながたくさんテストを作る前にコアとなるテスト用のfixtureは用意しておきます。 さもないと、みんなが好き勝手にfixtureを作ってしまい、あっという間に混乱に陥ります。 プログラム本体と同様に、DRYの原則で、同じようなテストデータを繰り返し作ってしまうようなことは避けるべきです。 最悪なパターンは、開発機や本番機のデータを引っこ抜いてきて、それをそのままテストデータとしてごっそり使う方法です。 流石に居ないと思いたいですが、こういうことをやってしまうとテストデータの見通しが悪すぎて、テストが失敗した場合の検証が非効率だし、そもそもデータのロードに時間がかかりすぎます。

共通のfixtureとテストケースごとのfixtureを分離する

マスター系のテーブルとか、ユーザー情報のテーブルのためのfixtureはテストケースごとにバラバラに用意してはいけません。 似たようなfixtureがあちこちに造られると、テストの数が増えてからの仕様変更の際にテストデータのメンテナンスだけでえらく時間が取られてしまうことになります。 僕の場合は、共通fixture置き場に基本的なfixtureをテーブルごとに配置した上で、テストケースごとのfixtureを上書きでロードしています。 以下はPHPの場合ですが、基底クラスで、以下のようにCSVの取り込みロジックを作っておきます。

protected function getDataSet()
{
    echo __FUNCTION__ . "\n";
    $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet();

    if(is_array($this->fixture_path)) {
        foreach($this->fixture_path as $path) {
            echo "----" . $path . "\n";
            if ($dir = opendir($path)) {
                while (($file = readdir($dir)) !== false) {
                    if ($file != "." && $file != ".." && substr($file, -4) == ".csv") {
                        $table_name = str_replace(".csv", "", basename($file));
                        echo "===loading $file...";
                        @$dataSet->addTable($table_name, $path . "/" . $file);
                        echo " done!\n";
                    }
                }
                closedir($dir);
            }
        }
    }
    return $dataSet;
}

そして、テストケースのコンストラクタあたりで

public function HogeTest()
{
    $this->fixture_path = array(dirname(__FILE__) . '/../fixture/',
        dirname(__FILE__) . '/./fixture/'
    );
}

のようにして、fixtureを複数階層に分けてロードしています。 fixtureをYAMLやXMLで持ってても同じことができるはずです。

fixtureの内容をチェックするためのツールを作る

僕はPHPUnit+Seleniumで結合試験を自動化していて、fixtureはテーブルごとにCSVファイルを用意しています。 これらのCSVをsetUp()で読み込むようにしていますが、fixtureのカラム数があっていないと、すぐテストが落ちてしまいます(XMLでfixtureを保持していると楽かもしれないですが)。 開発中は、当然カラムの追加や削除はありますが、その度にどのfixtureを修正しなければならないかを考えるのは面倒で仕方がありません。 したがって、僕は、全fixtureをDBのスキーマと照合するツールを作っています。 これがあればスキーマの変更の怖さはちょっと軽減されます。

fixtureを自動生成するようなツールを作る

手で整合性の取れたfixtureを作るのはなかなか大変なので、Excelでfixtureをつくるようにもしています。 予め用意しておいたシートに値を入れてマクロを動作させると、fixtureが自動で生成されます。 こういうツールもプロジェクトの早い段階で用意できていると効率的でしょう。

日付や時刻等で、テストの成功・失敗条件に関わるテストデータはfixtureとは切り離す

例えば最終ログインから60日たっていたらAをして、120日たっていたらBをする、というテストケースの場合、fixtureに最終ログイン日時をハードコーディングできません。 ハードコーディングしてしまうと、そのテストは実行日によってテスト結果が変わってしまうテストであり、自動テストの条件を満たさないからです。 このような場合は、fixtureにて識別可能な適当な値を設定しておいて、別途テストの初期化メソッドで値を書き換えるような対応をします。

それでは。