大変お待たせしました。遅ればせながら本年もよろしくお願いします。さっそく、
筆者はテスト駆動開発に興味を持ってはいたのですが、
- 複雑なものも扱えるのか
 - 生産性が本当に上がるのか
 - 部分をテストしても全体のテストのかわりにはならないのでは?
 - テスト可能にすると、
ソースが読みにくくなるのでは?  - 最初に構造をしっかり設計しないと、
あとから変更できないのでは?  
特に最後の
そこで、
わざと設計なしでプログラムを書く
使用するのは
さて、
- エラー発生時の行番号表示
 - 前方参照をきれいに実装できる
 
「前方参照」
また、
- テストに失敗した場合でも、
次のテストメソッドを実行する (Exceptionを使用しないバージョン)  - クラスでない、
普通の関数もテスト可能  - オブジェクトのメンバを少ない記述でテストできる機能
(php5のマジックメソッドを使用)  
オブジェクトのテストだけ、
 $obj =& new class1();
 assertisa("class1", $obj)
  ->var1_eq(1)
  ->method1_eqf(2);
var1_
紙面の都合上、
行番号のためのクラスを作る
さっそくですが、
おさらいになりますが、
class   inputbuffer {
        function        test1() {
                $obj =& new inputbuffer();
                assertisa("inputbuffer", $obj)
                        ->iseof_eqf(1);
        }
}
空のinputbufferを作り、
class   inputbuffer {
        function        iseof() {
                return 1;
        }
        function        test1() {
                $obj =& new inputbuffer();
                assertisa("inputbuffer", $obj)
                        ->iseof_eqf(1);
        }
}
しかし、
以下のように、
class   inputline {
        var     $line = "";
        function        inputline($line = "") {
                $this->line = $line;
        }
        function        gets() {
                return $this->line;
        }
        function        test1() {
                $obj =& new inputline("test");  /* (1) */
                assertisa("inputline", $obj)
                        ->gets_eqf("test");
                $obj =& new inputline("test2"); /* (2) */
                assertisa("inputline", $obj)
                        ->gets_eqf("test2");
        }
}
(1)で、
class   inputline {
        var     $line = "";
        var     $locate = "";
        function        inputline($line = "", $locate = "(unknown)") {
                $this->line = $line;
                $this->locate = $locate;
        }
        function        gets() {
                return $this->line;
        }
        function        getlocate() {
                return $this->locate;
        }
        function        test1() {
                $obj =& new inputline("test");
                assertisa("inputline", $obj)
                        ->gets_eqf("test");
                $obj =& new inputline("test2");
                assertisa("inputline", $obj)
                        ->gets_eqf("test2");
                
                $obj =& new inputline("test3", "line#1"); /* (3) */
                assertisa("inputline", $obj)
                        ->gets_eqf("test3")
                        ->getlocate_eqf("line#1");
                $obj =& new inputline("Hello TDD", "line#2"); /* (4) */
                assertisa("inputline", $obj)
                        ->gets_eqf("Hello TDD")
                        ->getlocate_eqf("line#2");
                
                $obj =& new inputline("test");   /* (5) */
                assertisa("inputline", $obj)
                        ->gets_eqf("test")
                        ->getlocate_eqf("(unknown)");
        }
}
(3)で行番号の情報をテストします。今回は、
トークンへの分解の方法
さて、
これもいくつか考えられますが、
class   inputline {
        var     $line = "";
        var     $locate = "";
        function        inputline($line = "", $locate = "(unknown)") {
                $this->line = $line;
                $this->locate = $locate;
        }
        function        gets() {
                $ret = $this->line;
                $this->line = "";
                return $ret;
        }
        function        ungets($s = "") {
                $this->line = $s.$this->line;
        }
        function        getlocate() {
                return $this->locate;
        }
        function        test1() {
/* .... */
                $obj =& new inputline("this is a test.");  /* (1) */
                assertisa("inputline", $obj)
                        ->gets_eqf("this is a test.")
                        ->gets_eqf("");
                
                $obj->ungets("abc");    /* (2) */
                assertisa("inputline", $obj)
                        ->gets_eqf("abc");
                
        }
}
(1)で、
class   inputline {
        var     $line = null;
        var     $locate = "";
        function        inputline($line = "", $locate = "(unknown)") {
                $this->line = $line;
                $this->locate = $locate;
        }
        function        gets() {
                $ret = $this->line;
                $this->line = null;
                return $ret;
        }
        function        ungets($s = "") {
                $this->line = $s.$this->line;
        }
        function        getlocate() {
                return $this->locate;
        }
        function        test1() {
/* .... */
                $obj =& new inputline("this is a test.");
                assertisa("inputline", $obj)
                        ->gets_eqf("this is a test.")
                        ->gets_eqf(null);  /* (4) */
                
                $obj->ungets("abc");
                assertisa("inputline", $obj)
                        ->gets_eqf("abc");
                
                $obj->ungets("def");  /* (3) */
                $obj->ungets("ghi");
                assertisa("inputline", $obj)
                        ->gets_eqf("ghidef")
                        ->gets_eqf(null);  /* (4) */
                
        }
}
(3)では、
続いて、
class	inputline {
/* .... */
        function        getchunk($terminator = "", $terminator2 = "") {
                $line = $this->gets();
                $ret = "";
                for (;;) {
                        if ($line == "")
                                return null;
                        $c = substr($line, 0, 1);
                        $line = substr($line, 1);
                        if (strpos($terminator, $c) !== FALSE)
                                break;
                        $ret .= $c;
                }
                while ($line != "") {
                        $c = substr($line, 0, 1);
                        if (strpos($terminator, $c) === FALSE)
                                break;
                        $line = substr($line, 1);
                }
                $this->ungets($line);
                return $ret;
        }
/* .... */
        function        test1() {
/* .... */
                $obj =& new inputline("label: decfsz INDF, 1 ; comment");  /* (1) */
                asserteq("label", $obj->getchunk(": "));
                asserteq("decfsz INDF, 1 ; comment", $obj->gets());  /* (2) */
                
        }
}
(1)では、
この段階のgetchunk()では、
class	inputline {
/* .... */
        function        getchunk($terminator = "", $eolenable = 0) {  /* (4) */
                $line = $this->gets();
                $ret = "";
                for (;;) {
                        if ($line == "") {
                                if (($eolenable))
                                        return $ret;
                                $this->ungets($ret);
                                return null;
                        }
                        $c = substr($line, 0, 1);
                        $line = substr($line, 1);
                        if (strpos($terminator, $c) !== FALSE)
                                break;
                        $ret .= $c;
                }
                while ($line != "") {
                        $c = substr($line, 0, 1);
                        if (strpos(" \t", $c) === FALSE)
                                break;
                        $line = substr($line, 1);
                }
                $this->ungets($line);
                return $ret;
        }
/* .... */
        function        test1() {
/* .... */
                $obj =& new inputline("label: decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));  /* (5) */
                asserteq("decfsz INDF, 1 ; comment", $obj->gets());
                
                $obj =& new inputline("label decfsz INDF, 1 ; comment");  /* (3) */
                asserteq("label", $obj->getchunk(": \t")); /* (5) */
                asserteq("decfsz INDF, 1 ; comment", $obj->gets());
                
                $obj =& new inputline(" decfsz INDF, 1 ; comment");
                asserteq("", $obj->getchunk(": \t"));   /* (5) */
                asserteq("decfsz INDF, 1 ; comment", $obj->gets());
                
                $obj =& new inputline("label:");
                asserteq("label", $obj->getchunk(": \t"));  /* (5) */
                asserteq("", $obj->gets());
                
                $obj =& new inputline("label");
                asserteq("label", $obj->getchunk(": \t", 1));  /* (5) */
                asserteq(null, $obj->gets());
                
        }
}
(3)では、
class	inputline {
/* .... */
        function        getchunk($terminator = null, $eolenable = 0) {
                if ($terminator === null) {
                        $terminator = " \t";
                        $eolenable = 1;
                }
                
                $line = $this->gets();
                $ret = "";
                for (;;) {
                        if ($line == "") {
                                if (($eolenable))
                                        return $ret;
                                $this->ungets($ret);
                                return null;
                        }
                        $c = substr($line, 0, 1);
                        $line = substr($line, 1);
                        if (strpos($terminator, $c) !== FALSE)
                                break;
                        $ret .= $c;
                }
                while ($line != "") {
                        $c = substr($line, 0, 1);
                        if (strpos(" \t", $c) === FALSE)
                                break;
                        $line = substr($line, 1);
                }
                $this->ungets($line);
                return trim($ret);
        }
/* .... */
        function        test1() {
/* .... */
                $obj =& new inputline("label: decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk()); /* (6) */
                asserteq("INDF, 1 ; comment", $obj->gets());
                
                $obj =& new inputline("label: clrwdt");  /* (7) */
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("clrwdt", $obj->getchunk());
                asserteq(null, $obj->gets());
                
                $obj =& new inputline("label: decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq("INDF", $obj->getchunk(","));
                asserteq("1 ; comment", $obj->gets());
                
                $obj =& new inputline("label: decfsz INDF"); /* (8) */
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq(null, $obj->getchunk(","));
                asserteq("INDF", $obj->gets());
                
                $obj =& new inputline("label\tdecfsz\tINDF\t,\t1\t;\tcomment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq("INDF", $obj->getchunk(","));
                asserteq("1", $obj->getchunk());
                asserteq(";\tcomment", $obj->gets());
                
        }
}
(6)で、
(8)で、
実は、
繰り返されるinput系の修正
やりかけだった、
class   inputbuffer {
        var     $linelist;
        function        inputbuffer() {
                $this->linelist = array();
        }
        function        iseof() {
                if (count($this->linelist) == 0)
                        return 1;
                return 0;
        }
        function        addcontents($contents, $name = "(unknown)", $startnumber = 1) {
                $a = array();
                foreach (split("\r|\n|\r\n", $contents) as $s)
                        $a[] =& new inputline($s, $name." #".($startnumber++));
                array_splice($this->linelist, 0, 0, $a);
        }
        function        &getline() {
                $obj =& $this->linelist[0];
                array_shift($this->linelist);
                return $obj;
        }
        function        test1() {
                $obj =& new inputbuffer();
                assertisa("inputbuffer", $obj)
                        ->iseof_eqf(1);
                
                $obj->addcontents("test1\ntest2\n"); /* (1) */
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test1");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test2");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("");
                
                $obj->addcontents("test3\ntest4\n");
                asserteq(0, $obj->iseof());
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test3");
                asserteq(0, $obj->iseof());
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test4");
                asserteq(0, $obj->iseof());
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("");
                asserteq(1, $obj->iseof());
                
                $obj->addcontents("test5\ntest6\n", "testfile.txt");
                assertisa("inputline", $obj->getline())
                        ->getlocate_eqf("testfile.txt #1");
                assertisa("inputline", $obj->getline())
                        ->getlocate_eqf("testfile.txt #2");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("");
                
                $obj->addcontents("test7\ntest8");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test7");
                $obj->addcontents("test9\ntest10");  /* (2) */
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test9");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test10");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("test8");
                asserteq(1, $obj->iseof());
                
        }
}
(1)以降で、
いったんaddcontents()のコードを書きますが、
さて、
class   commentlessinputline extends inputline {
        function        commentlessinputline($line = "", $locate = "(unknown)") {
                if (($pos = strpos($line, ";")) !== FALSE)
                        $line = substr($line, 0, $pos);
                parent::inputline($line, $locate);
        }
        function        test1() {
                $obj =& new commentlessinputline("test");
                assertisa("inputline", $obj)
                        ->gets_eqf("test");
                $obj =& new commentlessinputline("test2");
                assertisa("inputline", $obj)
                        ->gets_eqf("test2");
                
                $obj =& new commentlessinputline("test3", "line#1");
                assertisa("inputline", $obj)
                        ->gets_eqf("test3")
                        ->getlocate_eqf("line#1");
                $obj =& new commentlessinputline("Hello TDD", "line#2");
                assertisa("inputline", $obj)
                        ->gets_eqf("Hello TDD")
                        ->getlocate_eqf("line#2");
                
                $obj =& new commentlessinputline("test");
                assertisa("inputline", $obj)
                        ->gets_eqf("test")
                        ->getlocate_eqf("(unknown)");
                
                $obj =& new commentlessinputline("this is a test.");
                assertisa("inputline", $obj)
                        ->gets_eqf("this is a test.")
                        ->gets_eqf(null);
                
                $obj->ungets("abc");
                assertisa("inputline", $obj)
                        ->gets_eqf("abc");
                
                $obj->ungets("def");
                $obj->ungets("ghi");
                assertisa("inputline", $obj)
                        ->gets_eqf("ghidef")
                        ->gets_eqf(null);
                
                $obj =& new commentlessinputline("label: decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz INDF, 1 ", $obj->gets());
                
                $obj =& new commentlessinputline("label decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz INDF, 1 ", $obj->gets());
                
                $obj =& new commentlessinputline(" decfsz INDF, 1 ; comment");
                asserteq("", $obj->getchunk(": \t"));
                asserteq("decfsz INDF, 1 ", $obj->gets());
                
                $obj =& new commentlessinputline("label:");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq(null, $obj->gets());
                
                $obj =& new commentlessinputline("label");
                asserteq("label", $obj->getchunk(": \t", 1));
                asserteq(null, $obj->gets());
                
                $obj =& new commentlessinputline("label: decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq("INDF, 1 ", $obj->gets());
                
                $obj =& new commentlessinputline("label: clrwdt");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("clrwdt", $obj->getchunk());
                asserteq(null, $obj->gets());
                
                $obj =& new commentlessinputline("label: decfsz INDF, 1 ; comment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq("INDF", $obj->getchunk(","));
                asserteq("1 ", $obj->gets());
                
                $obj =& new commentlessinputline("label: decfsz INDF");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq(null, $obj->getchunk(","));
                asserteq("INDF", $obj->gets());
                
                $obj =& new commentlessinputline("label\tdecfsz\tINDF\t,\t1\t;\tcomment");
                asserteq("label", $obj->getchunk(": \t"));
                asserteq("decfsz", $obj->getchunk());
                asserteq("INDF", $obj->getchunk(","));
                asserteq("1", $obj->getchunk());
                asserteq(null, $obj->gets());
                
        }
}
class   inputbuffer {
/* .... */
        function        addcontents($contents, $name = "(unknown)", $startnumber = 1) {
                $a = array();
                foreach (split("\r|\n|\r\n", $contents) as $s)
                        $a[] =& new commentlessinputline($s, $name." #".($startnumber++));
                array_splice($this->linelist, 0, 0, $a);
        }
/* .... */
}
commentlessinputlineを作ったあと、
その後、
class   commentlessinputbuffer extends inputbuffer {
        function        addcontents($contents, $name = "(unknown)", $startnumber = 1) {
                $a = array();
                foreach (split("\r|\n|\r\n", $contents) as $s) {
                        if (($pos = strpos($s, ";")) !== FALSE)
                                $s = substr($s, 0, $pos);
                        $a[] =& new inputline($s, $name." #".($startnumber++));
                }
                array_splice($this->linelist, 0, 0, $a);
        }
        function        test1() {
                $obj =& new commentlessinputbuffer();
                $obj->addcontents("line;comment");
                assertisa("inputline", $obj->getline())
                        ->gets_eqf("line");
        }
}
コメントが取り除かれることを検証するテストを書いたあと、
このあとアセンブラのメインの処理を書いていくのですが、
入力にエラーがあってアセンブル処理を継続できない場合は、
class	inputbuffer {
        var     $linelist;
        var     $message = null;
/* .... */
        function        getmessage() {
                if (count($this->message) /* (1) */
                asserteq(null, $obj->getmessage());
                $obj->addmessage("error1");
                asserteq("error1", $obj->getmessage());
                $obj->addmessage("error2"); /* (2) */
                asserteq("error1\nerror2", $obj->getmessage());
                
        }
}
そこで、
アセンブル処理本体に渡るのは、
class   inputline {
        var     $line = null;
        var     $locate = "";
        var     $parent = null;
/* .... */
        function        setparent(&$parent) {
                $this->parent =& $parent;
        }
        function        addmessage($message = "") {
                if ($this->parent === null)
                        return;
                $this->parent->addmessage($message." in ".$this->locate);
        }
        function        test1() {
/* .... */
                $obj =& new inputline(); /* (1) */
                asserteq(null, $obj->parent);
                $obj2 =& new inputline();
                $obj->setparent($obj2);
                asserteq($obj2, $obj->parent);
                
                $obj =& new inputline();
                $obj->addmessage("dummy");
                
        }
}
class   inputbuffer {
/* .... */
        function        test1() {
/* .... */
                $obj =& new inputbuffer();
                asserteq(null, $obj->getmessage());
                $obj->addmessage("error1");
                asserteq("error1", $obj->getmessage());
                $obj->addmessage("error2");
                asserteq("error1\nerror2", $obj->getmessage());
                
                $obj->addcontents("test9", "testfile2.txt");  /* (2) */
                $line =& $obj->getline();
                asserteq($obj, $line->parent);
                $line->addmessage("error3");
                asserteq("error1\nerror2\nerror3 in testfile2.txt #1", $obj->getmessage());
                
                $obj->addcontents("test10\ntest11", "testfile3.txt");
                $line =& $obj->getline();
                $line =& $obj->getline();
                $line->addmessage("error4");
                asserteq("error1\nerror2\nerror3 in testfile2.txt #1\nerror4 in testfile3.txt #2", $obj->getmessage());
                
        }
}
(1)で、
さて、
テスト駆動開発は、設計変更に耐えられるのか 
ここまでの流れから、
複雑なものも扱えるのか?
→ 言語の構文解析は複雑。inputまわりを中心に見ていきましたので、
より複雑なものについては、
生産性が本当に上がるのか?
→ (これは未検証)生産性については、
また、
部分でテストしても全体の代わりになるのか?
→ アセンブラとして機能するのかをチェック。inputbufferからinputlineを含んだテストをするなど、
全体のテストが不要になるわけではありませんが、
テスト可能にするとソースが読みにくくなるのでは?
→ 前回のソースと比較。テストできるようにしたため、
たとえば、
また、
ですので、
最初に構造をしっかり設計しないと、あとから変更できないのでは? 
→ 何度も大変更を行った。これが本題です。今回は、
先ほどのsetparent()のように、
問題は、
もっと複雑なプログラム
というわけで、
なお、
