作者: Yさ
日時: 2004/3/31(13:51)
 「テスト駆動開発(Test-Driven Developnment)」ってご存知ですか?
 以前にtsfreeでボウリングのスコア計算の例をご紹介しましたが、簡単に言うと、
  プログラマはコードを記述する前にテストを書き、
  その後、テストに合格するのに必要なコードだけを書く
といった開発スタイルのことです。
 詳細はKent Beck 著「Test-Driven Developnment:By Example」を見てください。
 (JavaとPythonのコードが半分くらいを占めています)

 テスト駆動開発を、簡単な例でご紹介してみたいと思います。
 ...今回も awk で(^^;)


1.1 準備
 10進数値を与えるとローマ数字表現に変換する関数cnvRoman()を作るとします。
 XP(eXtreme Programming)ではxUnit等のテストの自動環境テスティング・フレーム
ワークが利用されますが、単純なテスト実行結果が確認できる骨組みを作ってみます。
 "関数を適当なテストケースで呼び出し、期待した結果かどうかを表示する"
といったものです。
-----
function cnvRoman(n){
  return "";
}


# テストルーチン
function eval(fname,test,val,  err){
  err=0;
  if(!(test==val)) err=1;

  printf("%s: ", ((err)?("err"):("OK ")));
  print fname "=" test,"==",val;
}
BEGIN{
  eval("#01: 1",cnvRoman(1),"I");
}
-----

 実行すると、
-----
C:\>gawk -f r.awk
err: #01: 1= == I
-----

 期待値が"I"なのに""だったというメッセージが出ています。
 とりあえず関数cnvRoman()を以下のようにしてみます。
-----
function cnvRoman(n){
  return "I";
}
-----

-----
C:\>gawk -f r.awk
OK : #01: 1=I == I
-----

 テストは問題なく終了します。

 ではテスト駆動開発(TDD)で10進数値を与えるとローマ数字表現に変換する関数を実装
してみましょう。


1.2 関数cnvRoman()の実装
 変換ルールを説明すると、文字I,V,X,L,Cで1,5,10,50,100を表し、それ以外の数を
文字を組み合わせて表現します。(もっと大きな数を表わす文字もあります。)
 また4や9をIVやIXと右の数から左の数を減じて表現します。これは右の数が左の数の
ちょうど5倍または10倍のときだけ使われます。
 以下のような感じです。
  1   I         :             :
  2   II       39  XXXIX     80  LXXX
  3   III      40  XL         :
  4   IV        :            90  XC
  5   V        43  XLIII      :
  6   VI       44  XLIV      99  XCIX
  7   VII      45  XLV       100 C
  8   VIII      :             :
  9   IX       49  XLIX      
  10  X        50  L         


 TDDではまず最初にテストケースを追加します。
-----
  eval("#01: 1",cnvRoman(1),"I");
  eval("   : 2",cnvRoman(2),"II");
  eval("   : 3",cnvRoman(3),"III");
  eval("   : 4",cnvRoman(4),"IV");
  eval("   : 5",cnvRoman(5),"V");
  eval("   : 6",cnvRoman(6),"VI");
  eval("   : 7",cnvRoman(7),"VII");
  eval("   : 8",cnvRoman(8),"VIII");
  eval("   : 9",cnvRoman(9),"IX");
  eval("   :10",cnvRoman(10),"X");
-----

 (省略しますが)テストを実行し、失敗することを確認します。
 TDDではテストが失敗している状態をRedと呼びます。
 例えば、Java用のxUnitであるJUnitのGUIでは赤いバーが表示されます。

 最もシンプルな、テストをパスしそうな実装をしてみます。
-----
function cnvRoman(n){
  if(n==1) return "I";
  if(n==2) return "II";
  if(n==3) return "III";
  if(n==4) return "IV";
  if(n==5) return "V";
  if(n==6) return "VI";
  if(n==7) return "VII";
  if(n==8) return "VIII";
  if(n==9) return "IX";
  if(n==10) return "X";
  return "";
}
-----

 (省略しますが)テストは成功します。
 これはTDDでFake Itと呼ぶ定石です。

 ちなみにテストが成功した状態をTDDではGreenと呼びます。
 JUnitなら緑のバーが表示されます。

 TDDではコードに重複があるならリファクタリングの余地が無いか見直します。
 'I'や'V'などが繰り返され重複しているのに気がつきます。それを解消しましょう。

-----  [リファクタリング途中]
function cnvRoman(n){
  if(n==1) return "I";
  if(n==2) return "I" "I";
  if(n==3) return "I" "II";
  if(n==4) return "I" "V";
  if(n==5) return "V";
  if(n==6) return "V" "I";
  if(n==7) return "V" "II";
  if(n==8) return "V" "III";
  if(n==9) return "I" "X";
  if(n==10) return "X";
  return "";
}
-----
 ↓
-----  ["I","V"の定数の重複除去、他]
function cnvRoman(n){
  if(n==1) return "I";
  if(n==5) return "V";
  if(n==10) return "X";
  if(n==4) return cnvRoman(1) cnvRoman(n+1);
  if(n==9) return cnvRoman(1) cnvRoman(n+1);
  if(n==2) return cnvRoman(1) cnvRoman(n-1);
  if(n==3) return cnvRoman(1) cnvRoman(n-1);
  if(n==6) return cnvRoman(5) cnvRoman(n-5);
  if(n==7) return cnvRoman(5) cnvRoman(n-5);
  if(n==8) return cnvRoman(5) cnvRoman(n-5);
  return "";
}
-----
 ↓
-----  [return文の重複除去、他]
function cnvRoman(n,  base){
  if(n==1) return "I";
  if(n==5) return "V";
  if(n==10) return "X";
  if(n==4 || n==9) return cnvRoman(1) cnvRoman(n+1);
  base=1;
  if(n>5) base=5;
  return cnvRoman(base) cnvRoman(n-base);
}
-----

 省略しますがリファクタリング途中で、テストが失敗しないか常に確認します。
 全てのテストに成功する事で、まずい事が起きていないのが確認できます。

 なお "機能の拡張"と"リファクタリング"は異なる概念なので、きちんと区別して
考える必要があります。TDDでのリファクタリングは重複の除去についてのみです。
 (蛇足:作り直しは"リストラクチャリング"です。)

(続く)