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

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

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

ページ順の編集

では,本番のページ順の編集です。ページを並び替えるのはpdfpagesというクラスです。テストの方法は,コンストラクタにダミーのpdfオブジェクトを渡し,makebooklet()メソッドを呼びます。そして,さきほどと同じ方法で,ページの順番をテストします。では,早速テストを書いてみましょうリスト7⁠。もちろん,みなさんが思いついたテストを追加しても構いません。また,当たり前ですがテスト対象のメソッドがないとエラーになります。そこで,何もしないメソッドを追加してあります。

リスト7 テストを最初に書く(target.php)

class	pdfpages {
        function        pdfpages(&$pdf) {
        }
        function        makebooklet() {
        }
}


class   pdfpages_test {
        function        test1() {
                $pdf =& new dummy_pdf("a b c d");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("d a b c", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("@ a b @ @ c d e", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e f");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("@ a b @ f c d e", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e f g");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("@ a b g f c d e", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e f g h");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("h a b g f c d e", $pdf->pagelist());
        }
}

注意点ですが,pdfpagesのコンストラクタは引数としてpdfオブジェクトを受け取ります。しかし,run.phpでは,引数なしでコンストラクタを呼びだしています。このため,pdfpagesの中にテストを書くと,引数なしでコンストラクタが呼ばれてエラーが発生してしまいます。これを避けるため,pdfpagesの中にはテストを置かず,別のクラスにテストを定義しています。

ちなみにPHP5以降では,pdfpages(&$pdf = null)のように,リファレンスの仮引数の省略時に,デフォルト値をnullにするといったことが可能で,この場合はテストをクラスの中に置くことができます。

実行すると,順番が元のままであるため,エラーになっています。makebooklet()の中で何もしていないので,当然ですね。

$ php run.php
........
Expected: "dabc"
Fonud: "abcd"
----
test failed.
$

では,makebooklet()の中を書いてみますリスト8⁠。お時間のある方は,自分で書いてテストを通してみるのもおすすめです。

リスト8 makebookletを書く(target.php)

class	pdfpages {
        var     $pdf = null;
        function        pdfpages(&$pdf) {
                $this->pdf =& $pdf;
        }
        function        &newblankpage() {
                $page =& $this->pdf->newPage();
                return $page;
        }
        function        makebooklet() {
                while ((count($this->pdf->pages) % 4))
                        $this->pdf->pages[] =& $this->newblankpage();
                
                $newpages = array();
                $left = count($this->pdf->pages) - 1;
                $right = 0;
                
                while ($right < $left) {
                        $newpages[] =& $this->pdf->pages[$left--];
                        $newpages[] =& $this->pdf->pages[$right++];
                        $newpages[] =& $this->pdf->pages[$right++];
                        $newpages[] =& $this->pdf->pages[$left--];
                }
                $this->pdf->pages = $newpages;
        }
}

実行すると,今度はテストに成功します。

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

あとは,PDFを扱う部分だけです。これには,冒頭で書いたようにZend Frameworkを使います。ダウンロードして展開したら,ソース中のパスを合わせてください。

Zend Framework
URL:http://framework.zend.com/

run.phpから呼ばれているときは,pdfの処理を飛ばさないといけません。そこで,asserteq()が定義されているかどうかを確認し,定義されていればクラス定義のみを実行し,pdf処理を行わないようにしましたリスト9⁠。

リスト9 pdf処理を含んだコード(target.php)

<?php

class   dummy_pdfpage {
        var     $name = "";
        function        dummy_pdfpage($name = "none") {
                $this->name = $name;
        }
        function        getWidth() {
                return 1;
        }
        function        getHeight() {
                return 1;
        }
        function        drawLine() {
        }
}


class   dummy_pdf {
        var     $pages;
        function        dummy_pdf($s = "") {
                $this->pages = array();
                if ($s != "")
                        foreach (explode(" ", $s) as $name)
                                $this->pages[] =& new dummy_pdfpage($name);
        }
        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);
        }
        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());
        }
        function        test2() {
                $pdf =& new dummy_pdf("a b c");
                asserteq("a b c", $pdf->pagelist());
                $pdf->pages[] =& new dummy_pdfpage("d");
                asserteq("a b c d", $pdf->pagelist());
                $pdf->pages[] =& $pdf->newPage();
                asserteq("a b c d @", $pdf->pagelist());
        }
}


class   pdfpages {
        var     $pdf = null;
        function        pdfpages(&$pdf) {
                $this->pdf =& $pdf;
        }
        function        &newblankpage() {
                if (count($this->pdf->pages) <= 0)
                        die("no page.\n");
                $p =& $this->pdf->pages[0];
                $page =& $this->pdf->newPage($p->getWidth().":".$p->getHeight());
                $page->drawLine(0, 0, 0, 0);    # avoid PDF error.
                return $page;
        }
        function        makebooklet() {
                while ((count($this->pdf->pages) % 4))
                        $this->pdf->pages[] =& $this->newblankpage();
                
                $newpages = array();
                $left = count($this->pdf->pages) - 1;
                $right = 0;
                
                while ($right < $left) {
                        $newpages[] =& $this->pdf->pages[$left--];
                        $newpages[] =& $this->pdf->pages[$right++];
                        $newpages[] =& $this->pdf->pages[$right++];
                        $newpages[] =& $this->pdf->pages[$left--];
                }
                $this->pdf->pages = $newpages;
        }
}


class   pdfpages_test {
        function        test1() {
                $pdf =& new dummy_pdf("a b c d");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("d a b c", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("@ a b @ @ c d e", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e f");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("@ a b @ f c d e", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e f g");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("@ a b g f c d e", $pdf->pagelist());
                
                $pdf =& new dummy_pdf("a b c d e f g h");
                $p =& new pdfpages($pdf);
                $p->makebooklet();
                asserteq("h a b g f c d e", $pdf->pagelist());
        }
}


if (!function_exists("asserteq")) {
        set_include_path(get_include_path().PATH_SEPARATOR."/download/ZendFramework-1.11.6/library/");
        require_once('Zend/Loader/Autoloader.php');
        Zend_Loader_Autoloader::getInstance();
        
        ini_set("max_execution_time", "300");
        ini_set("memory_limit", "1024M");
        
        $pdf =& Zend_Pdf::load("input.pdf");
        
        $p =& new pdfpages($pdf);
        $p->makebooklet();
        
        $pdf->save("output.pdf");
}

?>

また,pdf処理とテストの共通化に必要なメソッドをいくつか定義しました。

今回はユーザインターフェースについては相当手を抜いたので,input.pdfのページ順を入れ替えてoutput.pdfに出力するだけですが,うまく動いたでしょうか。コマンドライン引数にファイル名を指定できるようにするなど,いろいろ改良して使ってみてください。

エピローグ

いかがでしたでしょうか。実は,テスト駆動開発では,本当は今回のようにまとめてテストを書くやり方はしません。⁠いちばん重要な財産はテストであり,万一コードをすべて失ってしまったとしても,テストが無事なら元と同じ品質のコードをもう一度書くことができる」ためにはどんなことをやるのか,次回から取り上げていきたいと思います。よろしくお願いします。

著者プロフィール

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

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

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