« [NEWS] NUnit 2.6.2 リリース ~ async/await に対応! | トップページ | TDD Advent Calendar 2012 »

2012年12月24日 (月)

[コラム] テストファーストとは何か?

今年はネタがありません。
WP8 のテストファーストでもやろうかと思っていたら、 先を越されちゃったしw  (⇒ Phone Toolkit Test Framework を使ってみる(1) )
しょうがないので、 「プログラムの作り方」を教えるとしたらどうしたらいいだろう、 なんてことを気の向くままに書き散らしてみます。 まぁ、 なぜか最後は強引に TDD ネタに話を持って行きますけど f(^^;


● "Hello, world!"

「プログラムの作り方」を教えるとしたら、最初はやはり定番の「Hello, world!」から入るのでしょうね。

int main(int argc, char* argv[]) {
  printf("hello world!\r\n");
  return 0;
}


今だと

Textbox1.Text = "Hello, world!";

…かな。

だけどこれは「プログラムの作り方」ではありません。
絵筆を使って絵具を塗る手順を学んでいるだけ。 まだ自分一人でプログラムは作れないでしょう。

● 「プログラムの作り方」入門

「プログラムの作り方」入門を書くとしたら、どうなるだろう?

コンセプト:
"Hello, world!"を表示するプログラム

スペック(仕様):

  • スタート画面のタイルをタッチすると、「"Hello, world!"画面」が表示される。
  • スタート画面のタイル
      [画像 --- 略]
  • "Hello, world!"画面
      [画像 --- 略]

ここまでが、「何を作るか」の定義。本来は外部設計と呼ぶらしい。
大事なことは、「スペック(仕様)」は検証可能であること。 そうでなければ、 スペック通りに完成したのかどうか判定不能、 つまり、 終わりが分からない。 そういうのは、コンセプトとかアイデアとか呼ぶ。

さてここから先は、 「どうやって作るか」。 本来は内部設計と呼ぶはず。

アーキテクチャ:

  • VS2012の「新しいアプリケーション(XAML)」テンプレートの構造のままとする。

アーキテクチャをゼロから構築するわけではないので、改修ポイントとそのスペックを洗い出すだけで済む。

改修ポイント:

  • MainPage.xaml
    TextBlock を追加。
    スペック: フォントサイズ等は全体スペックを満たすこと
  • MainPage.xaml.cs の OnNavigatedTo メソッド
    スペック: メソッド終了時には、xaml の TextBox の Text プロパティに "Hello, world!" と設定されていること

…で、 ようやく MainPage.xaml と MainPage.xaml.cs の実装の話となる。

そして、

検証:

  • スタート画面のタイル
  • スタート画面のタイルをタッチすると、「"Hello, world!"画面」が表示される。
  • "Hello, world!" 画面

当初のスペックを満たすプログラムになったので、完成♪

…これくらい説明すれば、「プログラムの作り方」入門と言えるんじゃないかな。

ポイントは、『検証可能な形で「何を作るか」を決めてから、「どうやって作るか」に取り掛かる』こと。 仕事としてのモノ作りでは、当たり前の手順。
趣味とか遊びとか学習とかなら、 前半をいい加減にしたり飛ばしちゃったりしてもいいんだけどf(^^;
前半をすっ飛ばして検証不能なまま「どうやって作るか」に突入しちゃうと、ゴールが無いのだから仕事がいつになっても終わらない。

● もう少し難しい「プログラムの作り方」

「プログラムの作り方」入門のその次は…?
検証には合格したんだけど、コンセプトや全体スペックを変更したくなって、作り直すパターンかな。

そして、 もう少し難しい「プログラムの作り方」に進む。

コンセプト:
フィボナッチ数列を表示するプログラム

スペック(仕様):

  • スタート画面のタイルをタッチすると、「フィボナッチ画面」が表示される。
  • スタート画面のタイル
      [画像]
  • フィボナッチ画面
      [画像]
  • 「フィボナッチ画面」のボタンをタッチすると、フィボナッチ数列の次の項が表示される
  • フィボナッチ数列:
      1, 1, 2, 3, 5, 8, ... (F(n-2)+F(n-1)), ...

これでスペックは完璧?
ぉっと、無限に動かすのはムリゲーw

  • (追加スペック) 第20項までは表示できるものとする

※ こんなふうにしておけば、テストで桁あふれするまでボタンクリックしなくて済むし、try ~ catch も不要 f(^^;

検証可能なスペックを決めたので、 中身に入っていく。

アーキテクチャ:

  • VS2012の「新しいアプリケーション(XAML)」プロジェクト テンプレートの MainPage.xaml を「基本ページ」ページ テンプレートに置き換える
  • フィボナッチ数列を提供するクラスを置く。Fibonacci クラス

MainPage.xaml   MainPage.xaml.cs    
  [ボタン]      →    button1Click()   →   Fibonacci クラス

アーキテクチャでパーツに分解したら、またパーツごとに「何を作るか」→「どうやって作るか」を繰り返す。
それでは Fibonacci クラスから。

コンセプト:
フィボナッチ数列を提供するクラス

スペック:
・メソッドをひとつ持つ
  public static IEnumerable<int> Sequence()
  返値を順に読み出すと、1,1,2,3,5,8,...
  返値の中には、少なくとも20番目まで入っている

で、 実装に進むわけだけど、 ここでは省略。
実装が終わったら…

検証:

…さて、どうやりましょうか?

● 出来上がったパーツを検証する

検証して合格しなければ、 「Fibonacci クラスが完成した」とは言えません。
※ 全体を検証して「Fibonacci プログラムが完成した」とは言える。

ここが分かれ道かもしれません。
アーキテクチャを考え、 パーツに分解し、 パーツごとのスペックを決めて来たのですが…

  • パーツごとに完成したかどうかはどうでもいい、 全体でちゃんと動けばいいのよ♪
  • パーツごとに完成したかどうかも検証すべきだ。 そうしないと、おそらく全体としてもちゃんと動かないだろう。

前者の道はけっきょく「パーツごとのスペックはどうでもいい、 決めなくてもかまわない」となり、 それは「アーキテクチャはどうでもいい、 決めなくてもかまわない」と進んでいくでしょう。 だって、 決めたことを実現できているかどうか検証しないと言うんですから、 決めること自体に意味が無くなってきます。

後者の道を進みたいのですが、 すると「どうやって検証するか?」という課題が突き付けられます。
コンソールプログラムを書いて、 検証対象を呼び出してその結果を printf してもクリアできます。 けれど今はたいがいテスティングフレームワークがあるので、 それを使いましょう。 以下は C# と NUnit Framework の例。

[TestCase(1, 1)]
[TestCase(2, 1)]
[TestCase(3, 2)]
[TestCase(4, 3)]
[TestCase(5, 5)]
[TestCase(6, 8)]
public void SequenceTest01_1から6まで(int n, int F)
{
    int result = 0;
    for (int i = 0; i < n; i++)
    {
        result = Fibonacci.Sequence().Skip(i).First();
    }
    Assert.AreEqual(F, result);
}

[TestCase]
public void SequenceTest02_第20項()
{
    int result = Fibonacci.Sequence().Skip(19).First();
    Assert.IsTrue(result > 0); //値が取れていることが確認できれば良い
}


…はて? この検証コードって、メソッドのスペックをコードで表現しただけでは?
だったら、スペックを最初からこう書いておけば良いよね!!

ここまでのストーリーは、仕事でのモノ作りとしての「プログラムの作り方」として、 ごく当たり前のことのはずです。
「何を作るか」を決めてから「どうやって作るか」に取り掛かる、
分解した場合には再びそれを繰り返す、
作ったらスペックを満足できたか検証する、
ちょっと便利なツールがあったので少々自動化してみました、 というだけ。

● パーツのスペック書きを手抜きする

さて。
スペックを全部書き下すのは、 シンドイのです。 メソッドごとに全部スペックを書き出すのは大変なんです。 だけどヘタに手を抜いてサボると、 出来損ないのパーツになって、 完成した全体も出来損ないになってしまいます。

ちゃんとモノは出来上がるような手の抜き方は無いだろうか?

…あります!
上記のような発想を Kent Beck がしたのかどうかは分かりませんが、 こうすればありったけのスペックを書き出さなくても済みます。 ("Test Driven Development: By Example" より、 TDD の定義の前半)

Write new code only if an automated test has failed
(自動テストが失敗した場合だけ、 新しいコードを書く)

「自動テスト」というのは、 先ほどテスティングフレームワークで書いたメソッドの検証コード、すなわちコードで記述したスペックのことです。
これは言い換えると、

新しいコードを書くために必要となるスペックだけを書く

ということです。

● テストファーストの意味

まとめましょう。
Kent Beck の提唱したテストファーストとは、 じつはこういう事だったのです。

  • 当たり前の作り方である。 (「何を作るか」を決めてから「どうやって作るか」に取り掛かる、大きなものは分解して繰り返す)
  • スペックを自動的に検証できるように書くことで、 二度手間を省く。
  • スペックをありったけ書いたりはしない。 効率よく手抜きをして最小限で済ます。

余談。
こうやって書いてみると、 TDD って当たり前の事をやっているだけのような気がしてきます。
では、 モノ作りの大先輩である製造業では、 なぜやっていないのでしょう?
これも答は簡単です。 製造業の製品開発においては、 スペックの一部分だけを実装して検証することは不可能だからです。 実体のあるハードウェアの宿命ですね。

|

« [NEWS] NUnit 2.6.2 リリース ~ async/await に対応! | トップページ | TDD Advent Calendar 2012 »

*コラム」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/209349/56385719

この記事へのトラックバック一覧です: [コラム] テストファーストとは何か?:

« [NEWS] NUnit 2.6.2 リリース ~ async/await に対応! | トップページ | TDD Advent Calendar 2012 »