テストを“いちばん重要な財産”と考えると見えるもの

第2回 本当にコードを失っても大丈夫なのか,確かめてみよう

この記事を読むのに必要な時間:およそ 8 分

最初は1つのテストから

まず,コンストラクタのテストです。後半で必要になるので,テスト対象のクラスであるrailを継承して,テスト用のクラスを用意しました。まず,コンストラクタの引数が省略されたときのテストを書きます。オリジナルのコード(リスト2)を見ると省略時は"00"が渡されることになっているので,これをテストします。

前回はテストをまとめて書きましたが,テスト駆動開発では本当は,小さなテストを1つだけ書きます。そして,このテストが失敗するのを確認してから,テストを通すためだけのコードを書きます。まず,テストはリスト4です。

リスト4 テストを1つだけ書く

#! /usr/local/bin/php
<?php
$url = "http://localhost/gh/rail/simrail.php5";

class   rail {
}


class   testrail extends rail {
        function        test1() {
                $obj =& new testrail();
                asserteq("00", $obj->number);
        }
}


if (function_exists("asserteq"))
        return;


#
# |---r4---|---r3---|---r2---|---r1---|---r0---|
#

$r0 =& new rail("00");

〈以下省略〉

実行すると,きちんとテストに失敗します(というか,メンバ変数が未定義なのでPHPの警告が出ます⁠⁠。

では,コードを書きましょう。テストを通すための最低限のコードは,以下のようになりました。

        class   rail {
                var     $number = "00";
        }

「ふざけてるんじゃないか」と思われるかもしれませんが,これがテスト駆動開発のやり方です。少なくとも,こうすることで「コードをすべて失ってしまったとしても,テストが無事なら元と同じ品質のコードをもう一度書くことができる」に一歩近づくというのは,このあとを読めば同意していただけると思います。

ちなみに,実行すると,きちんと成功します。

$ php run.php rail21.php
.
test successed.
$

さすがに,このコードではあんまりですので,テストを追加します。コンストラクタに引数を渡しましょう。これも,一度に1つだけです。テストが失敗することを確認してください。

                $obj =& new testrail("12");
                asserteq("12", $obj->number);

このテストを通すためのコードを書きます。やっと,まともになりました。

class   rail {
        var     $number;
        function        rail($number = "00") {
                $this->number = $number;
        }
}

次はdrive()メソッドですが,これはちょっと工夫が必要です。file_get_contents()という関数を使って,別のサーバにアクセスしているからです。もちろん,テスト用のサーバを用意してもいいのですが,ユニットテストの範囲を外れますし,なるべく身軽にしたいと思いました。

そこでgethttp()というメソッドを用意し,テスト用のクラスではこれを偽物に差し替えます(専門的にはモックといいます)。今回はできるだけ簡単にし,渡されたURLをメンバ変数に格納するだけにします。gethttp()が呼び出されたあと,正しくメンバ変数がセットされているかをテストする,というやり方です。

これも,まず簡単なテストを書いて,失敗させますリスト5⁠。

リスト5 gethttp()のテスト

#! /usr/local/bin/php
<?php
$url = "http://localhost/gh/rail/simrail.php5";

class   rail {
        var     $number;
        function        rail($number = "00") {
                $this->number = $number;
        }
}


class   testrail extends rail {
        function        test1() {
                $obj =& new testrail();
                asserteq("00", $obj->number);
                $obj =& new testrail("12");
                asserteq("12", $obj->number);
                
                $obj->gethttp("http://a.b/");
                asserteq("http://a.b/", $obj->gethttpurl);
                
        }
}


if (function_exists("asserteq"))
	return;


#
# |---r4---|---r3---|---r2---|---r1---|---r0---|
#

$r0 =& new rail("00");

〈以下省略〉

テストを通すためのコードを書きます。

        var     $gethttpurl;
        function        gethttp($url) {
                $this->gethttpurl = $url;
        }

ちなみに,これとは逆に,テスト側が指定した値をモックから返したいときは,テスト側で返す値を変数にセットしておき,モック側ではその変数の値をreturnに指定するのが簡単なやり方です。また,もし1つの関数内で何度もgethttp()が呼ばれる場合,今回のやり方では変数が上書きされてしまいます。そこで,配列変数に格納するとよいでしょう。

著者プロフィール

木元峰之(きもとみねゆき)

独立系ソフトハウスに8年間勤務,パッケージソフトの開発や記事執筆などを行う。現在はフリーのコンサルタント。SWESTなどのワークショップで分科会のコーディネータを務める。デジタル回路設計歴30年,プログラミング歴27年。

きもと特急電子設計
URL:http://business.pa-i.org/