「テスト駆動開発(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でのリファクタリングは重複の除去についてのみです。
(蛇足:作り直しは"リストラクチャリング"です。)
(続く)