[[解説]] * テストケースの書き方 OSLの開発はテストを書きながら行う。テストをきちんと書くことには、以下のようなメリットがある: - デバッグの時間が減る (瑣末なミスはコンピュータが発見してくれるため) - 他人が理解しやすいコードになる (関数の呼び出し方や振舞が明示されているため) - やんごとなき事情で途中で中断することになってしまっても、自分が何をしていたか思いだしやすい (コードを少しずつ書くから) - リファクタリング(大改造)をする場合の労力が減る (今までと挙動が同じかどうかを自動で確認できるため) ** 手順 +何か関数(メソッド)を作ったら^H^H^H^H作る前に、テストを書く +何か関数(メソッド)を%%作ったら%%作る前に、テストを書く +最初は空の関数を作って、テストに失敗することを確認する (テストが動いていないだけのことを確認するため) +機能を増やしたら^H^H^H^H^H増やす前にテストを増やす +テストに通るように関数を作る +機能を%%増やしたら%%増やす前にテストを増やす +定期的にテストする ** 実例 「空成り」を判定する bool isKaranari(Move); という関数を作りたくなったとする。空成りとは将棋用語で - 駒を取らず - 成る 指手のことである。 *** まずテストファイルを作る osl/test/test-template.t.cc をコピーして karanari.t.cc を作る cd osl/test cp test-template.t.cc karanari.t.cc template を karanari に置換する。 ESC % Template RET Karanari - クラス名は通常大文字で始める。 - ファイル名はクラス名の先頭を小文字にしたものにする。 - テスト用のファイル名は Test.cc または .t.cc で終わらせる。簡潔なので後者を推奨 - 忘れずに cvs add する *** テストを書く まずは駒を取るかどうかに注目したテストを書いてみる。 簡単なテストから始めるのがミソ。いきなり全ての場合を尽くしたテストを書くのは大抵は無謀。 void testKaranari() { const Move m2823RY = newMove(newMove(newPosition(2,8),newPosition(2,3),PROOK,PTYPE_EMPTY,true,BLACK); // 23飛車成 (駒をとらない) CPPUNIT_ASSERT(isKaranari(m2823RY)); } void testNotKaranari() { const Move m2823RY_PAWN = newMove(newMove(newPosition(2,8),newPosition(2,3),PROOK,PAWN,true,BLACK); // 23飛車成 (歩を取る) CPPUNIT_ASSERT(! isKaranari(m2823RY_PAWN)); } 以上の二つのメソッドをpublic:以下の部分に挿入する。 - CPPUNIT_ASSERT は引数が真であることをテストするためのマクロ また、クラスの先頭の部分に、メソッド名を記述する。 CPPUNIT_TEST_SUITE(TemplateTest); CPPUNIT_TEST(testKaranari); CPPUNIT_TEST(testNotKaranari); CPPUNIT_TEST_SUITE_END(); ここに書かないと、テストは実行されないので注意。 *** コンパイルする make karanari.t この時点では、isKaranari() 関数ができていないので、当然コンパイルエラーになるはずである。 *** ダミー関数を書いてコンパイルを通す いよいよ関数を書き始めるがまだ本物を作らない。 まずは必ず真を返す関数を作る。 karanari.h に /** 空成りを判定する */ inline bool isKaranari(Move) { /* fake version */ return true; } と定義して、 make karanari.t とする。今度はめでたくコンパイルが通るはずである。 - /** */ で関数に関するコメントを書く (Doxygenが抽出する) - 未完成(嘘実装)の間は fake version というコメントを目印にいれておく 続いてテストを通すことに目を向ける。 testKaranari()はパスするが、testNotKaranari() は失敗する。 実は失敗しないと問題である。なぜならテストが間違っていたことになるから。 初めから正しい関数を書かなかった理由はテストをテストするためでもある。 続いて、これを改良してゆく。 *** 関数の改良 空成りの条件の一部の「駒を取らない」部分を書いてみる。 inline bool isKaranari(Move m) { /* fake version */ return getCapturePtype(m) != PTYPE_EMPTY; } 今度はめでたくテストが通る。 しかし、まだ「成る」部分の判定が入っていないため、テストは通ったものの完成したわけではない。そこで... *** テストの改良 「駒を取るが成らない」指手に付いてのテストを testNotKaranari() に追加する。 const Move m2823HI_PAWN = newMove(newMove(newPosition(2,8),newPosition(2,3),ROOK,PAWN,false,BLACK); // 23飛車不成 (歩を取る) CPPUNIT_ASSERT(! isKaranari(m2823HI_PAWN)); コンパイルしてテストに失敗することを確認する。 失敗したということは「テストが正しく書けた」ということである。 *** 関数の改良 「成る」部分の条件を追加する。 inline bool isKaranari(Move m) { return (getCapturePtype(m) != PTYPE_EMPTY) && getPromoteMask(m); } *** テスト 新しく書いたテストをパスできることを確認する。このように + 新たに機能を書く前にテストを作る + テストを実行して、テストに失敗することでテスト自体が正しく書けていることを確認する + テストに対応する機能を書く + テストを実行して、機能が正しく書けたかどうかを確認する というステップを繰り返す。 *** テストの自動化 Makefile に登録することで testAll を実行した時に、このテストも実行されるようにする。 BOARD_OBJS = ... karanari.t.o 登録する場所は4種類あるが、この場合は BOARD_OBJS の末尾に追加しておく。 * 参考文献 - テスティング http://www.objectclub.jp/technicaldoc/testing/ - エクストリームプログラミング http://www.objectclub.jp/community/XP-jp/xp_relate/xp-intro - (小道具を参考にしたい) http://www.mamezou.com/tec/Tips/xpExperienceOnSqueak/xpExperience.html