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

第1回 テスト“だけ”を使ってコードを再現するのは難しい?

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

プロローグ

読者の方で,次のように思っていらっしゃる方は,どれくらいいるでしょうか。

  • 「いちばん重要な財産はコードであり,万一コードを失ってしまったら,元と同じ品質のコードをもう一度書くのはとても大変だ」

筆者も長年このように思っていました。では,もし以下のように考えたらどうでしょう。

「いちばん重要な財産はテストであり,万一コードをすべて失ってしまったとしても,テストが無事なら元と同じ品質のコードをもう一度書くことができる」

今までとずいぶん違う考え方ですね。いろんな声が聞こえてきそうです。

  • 「コメントはどうするんだ」
  • 「テストが複雑すぎて保守できなくなったらどうするんだ」

ごもっともです。テスト駆動開発は万能ではありません。うまく適用できない場面もあり,このときは従来どおりのやり方が必要です。たとえば,デバイス制御(あるタイミングでI/Oポートを叩くとか)などは,保守可能なテストを書くのが割に合わない分野です(この疑問こそが,筆者が長年TDDをやってこなかった理由でもあります)⁠

でも,もし「いちばん重要な財産がテストだったら」にちょっとでも興味があるなら,別の考え方に触れてみるのもいい刺激になるかも知れません。本稿では,以前筆者が別の記事で取り上げたコードをテスト駆動ベースに移行していき,どのようなメリット・デメリットがあるのかを見ていこうと思います。よろしくお付き合いください。

ブックレット作成プログラム

では最初に,⁠入力されたPDFのページを,中綴じブックレット用に並び替えて出力する」というPHPプログラムを作ってみましょう。具体的には,図1のようにページ順を編集します。2面づけ・両面で印刷し,2つに折ってホチキスで綴じれば,ブックレットのできあがりです。難しいプログラムではありませんが,正しく並び替えられるかどうかちょっとテストしたい内容ですね。

図1 ブックレットのページ順

図1 ブックレットのページ順

このテストで使うのは,run.phpというスクリプトですリスト1)⁠こんなに短くて役に立つのかと思われるかも知れませんが,これだけです。毎回入力してもいいくらいの長さですね。

リスト1 テスト実行スクリプト(run.php)

#! /usr/local/bin/php
<?php

function        asserteq($expected, $found) {
        if ($expected === $found) {
                printf(".");
                return;
        }
        printf("\nexpected: \"%s\"\nfound: \"%s\"\n----\ntest failed.\n", $expected, $found);
        die();
}

require("target.php");

foreach (get_declared_classes() as $classname) {
        foreach (get_class_methods($classname) as $methodname) {
                if (!ereg("^test", $methodname))
                        continue;
                $obj =& new $classname();
                $obj->$methodname();
        }
}
print"\ntest successed.\n";

?>

asserteq()というのは,2つの引数が同じかどうかをテストする関数です。ひとまず,同じでなければエラーを表示して実行が止まるようにしました。

run.phpは,途中でtarget.phpをrequire()しています。これは,run.phpを起動すればテスト実行,target.phpを起動すれば通常の実行と,簡単に切り替えられるようにするためです。私個人は,run.phpの引数にテストするスクリプトのファイル名を渡すようにして使っています。最後に,⁠test」で始まるメソッドを順番に呼び出しています。

一方,テストされるtarget.phpでは,pdfのページをオブジェクトにして配列変数に入れます。これにはZendFramework(後述)を使います。この配列変数を入れ替えることで,ページ順を編集します。つまり,テストのイメージとしては,こんな感じになります。

  • 最初のページ順は "a b c d e f g h"
  • ブックレット順に並び替え
  • ページ順が"h a b g f c d e"になっているかテスト

そうすると,ページオブジェクトの配列を簡単に確認できる必要があります。こういうときは,テスト用のページオブジェクトを用意するのが簡単です。このオブジェクトは,⁠a」「b」といった,ページの名前を保持します。

そして,ページオブジェクトを保持しているpdfオブジェクトにもテスト用オブジェクトを作ります。こちらは,保持しているページの名前を連結して「a b c」のようにして返します。これで,テストが簡単にできるようになります。また,ページ数が4の倍数でないときのために,空白のページを付け加える機能も用意します(空白のページの名前は「@」にしました)⁠こうしてできたのが,リスト2です。

リスト2 テスト用オブジェクト(target.php)

<?php

class   dummy_pdfpage {
        var     $name = "";
        function        dummy_pdfpage($name = "none") {
                $this->name = $name;
        }
}


class   dummy_pdf {
        var     $pages;
        function        dummy_pdf() {
                $this->pages = array();
        }
        function        &newPage() {
                $page =& new dummy_pdfpage("@");
                return $page;
        }
        function        pagelist() {
                $array = array();
                foreach ($this->pages as $key => $dummy)
                        $array[] = $this->pages[$key]->name;
                
                return implode(" ", $array);
        }
}

?>

さっそくページの並べ替えのテストをしたいところですが,このテスト用オブジェクトに不具合があったら,ページ順の並べ替えのテストにも支障が出てしまいます。本当に正しく動くのか,ちょっとテストしたいですよね。

では,簡単にテストしてみましょう。テスト用のpdfクラスの中に,test1()というメソッドを作ります。このメソッドはテスト専用で,通常の動作時には呼び出されません。ここで,以下の処理を行います。

  • pdfオブジェクトを生成。
  • リストが""であることをテスト。
  • 「a」というページを追加。
  • リストが"a"であることをテスト。
  • 「b」というページを追加。
  • リストが"a b"であることをテスト。
  • newPage()を呼び出して,空のページを追加。
  • リストが"a b @"であることをテスト。
  • もう一度newPage()を呼び出して,空のページを追加。
  • リストが"a b @ @"であることをテスト。

これでうまくいけば,ページ順の入れ替えの処理も安心して書けますね。プログラムとしては,リスト3のようになります。ここではnew dummy_pdf()として新しいオブジェクトを作りましたが,今回はコンストラクタに引数もなく,途中で再初期化もしないので,$thisを使っても構いません。

リスト3 テストを行うメソッド(target.php)

class	dummy_pdf {

....

        function        test1() {
                $pdf =& new dummy_pdf();
                asserteq("", $pdf->pagelist());
                $pdf->pages[] =& new dummy_pdfpage("a");
                asserteq("a", $pdf->pagelist());
                $pdf->pages[] =& new dummy_pdfpage("b");
                asserteq("a b", $pdf->pagelist());
                $pdf->pages[] =& $pdf->newPage();
                asserteq("a b @", $pdf->pagelist());
                $pdf->pages[] =& $pdf->newPage();
                asserteq("a b @ @", $pdf->pagelist());
        }
}

では,実行してみましょう。

$ php 
.....
test successed.
$

asserteq()が呼ばれて成功すると「.」が1つ表示されます。表示を見ると,5つのテストか実行されたことがわかります。これで,安心してページ順を並び替えるという仕事に入ることができます。

著者プロフィール

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

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

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

コメント

コメントの記入