[Team System|TDD] テスト駆動開発を試してみよう(1)
昨年の12月21日に、当時展開していた MSDN オフラインセミナー 全国ツアー <チーム開発編>にてデモンストレーションしていたソースコードの一部を公開させていただきました。
「後日に手順を投稿します!」と言っておきながら、今だに投稿しておりませんでしたが、昨日に Visual Studio 2008 Ready Day も終わり、ちょっと時間がとれたので「今のうちに!」ということで投稿させていただきます。お待ちいただいていた皆様、お待たせしました。そして遅れてすみませんでした(^^;
第一回では、テスト駆動開発を試す前段階として、単体テストの意義について体感していただこうと思います。「前置きはいいから TDD!」という方は、第二回 だけをご覧ください。
なお、投稿中のスクリーンショットなどはクリックすると大きなサイズで表示できます。
ステップ0. ソースコードの入手
まだ、ソースコードをゲットしていない方(前にゲットしたけどもうゴミ箱に入れちまったよという方も)下記の MSDN オフラインセミナー フォローアップ ページよりダウンロードしてください:
[デモで使用したソースコード] というところからダウンロードいただけます。
ファイル名は、Seminar.zip となるはずです。これを適当なフォルダに解凍してください(この投稿では D:\Temp\ に解凍したものとします)。
ステップ1. Visual Studio 2008 でソリューションを開く
解凍したフォルダ以下の D:\Temp\BanckAccount\Banking.sln を開きます。
ソリューション エクスプローラに、3つのプロジェクトが表示されてます。
- BankAccount プロジェクト:
銀行口座を扱うクラスライブラリです。これがプロダクションのコードに該当します。 - BankAccount.ManualTest プロジェクト:
BankAccount クラスのテストをお手製のGUIで行うような例です。 - BankAccount.Test プロジェクト:
BankAccount クラスの単体テストのためのテストプロジェクトです。
ステップ2. BankAccount の構成を確認する
BankAccount プロジェクトの BankAccountModel.cd を開いてください。BankAccount のクラスダイアグラムが表示されます。こちらをご覧いただければ、一目瞭然ですが、以下のようなものが構成されていることが確認できます:
- フィールド:
- _currentBalance: float ... 預金残高(private)
- _currentLoan: float ... ローン残高(private)
- プロパティ:
- CurrentBalance: float ... 預金残高を返す読み取り専用のプロパティ(public)
- CurrentLoan: float ... ローン残高を返す読み取り専用のプロパティ(public)
- メソッド:
- BankAccount() ... コンストラクタ
- 引数なし ... _currentBalance, _currentLoan ともに 0 に設定
- 引数1つ(float) ... _currentBalance に初期値を設定。_currentLoan は 0 に設定
- 引数2つ(float, float) ... _currentBalance, _currentLoan に初期値を設定
- DepositMoney() ... 預金処理メソッド(public)
- MakePayment() ... 払い戻し処理メソッド(public)
- checkBalance() ... 残高とローンのバランスチェック(実は未使用...)(private)
- BankAccount() ... コンストラクタ
ちなみに、この銀行はドルだてになってます(だから型が float)。
ちなみに、Visual Studio のクラスダイアグラムは、UML のそれとは異なります。クラス ダイアグラムという名前になっていますが、その実際は、.NET の型をすべて示しています。したがってクラスだけではなく構造体や列挙型なども表現できるのですね。これらはコードと同期がとれていますのでコードでもモデルでも実状を把握できます。
ステップ3. DepositMoney() を確認する
次に預金処理のメソッドを確認してみましょう(クラス ダイアグラムで DepositMoney() をダブルクリックするとコードの該当箇所が表示されます)。
ロジックの中身は、ここで説明する必要はないでしょう(皆さんならパッと見ただけでわかりますので)。この銀行の預金処理は銀行に都合がいいようにできています(^^) 預金したときに、ローンがあったら、まず勝手にローンを返済するようになっています(ちなみにこの銀行は利子という概念がありません。だからローンは早く返してもらう方が銀行に都合がいいということで...)。
ステップ4. 単体テスト機能を使う意義を確認する
さて、ここで問題です。この DepositMoney() のロジックって本当に正しいかどうかどのように証明しますか?たとえば、あなたがこのコードを実装したとして、プロジェクト管理者やテスト担当者に「コードの品質どうよ?」と聞かれたらどう答えるでしょうか?
「いや、徹夜して開発したから無問題」
「脳内で何度もテストしてるから無問題」
「私はプロだ!そんなやぼなこと聞くな!」
どれもこれも気持ちはわかりますが、説得力は残念ながらありませんよね?
次に、BankAccount.ManualTest プロジェクトを選択して、デバッグ実行してください。
簡単な GUI アプリケーションが起動しました。これは何かというと、DepositMoney() をテストするための簡易アプリケーションとなります。これを使って、InitialBalance に預金残高の初期値を、DepositAmount に 預金額を、それぞれ入力して、[DepositMoney()] ボタンを押下すると CurrentBalance に DepositMoney() を実行した結果が出力されるというものになります。
実際の BankAccount のオブジェクトの生成や DepositMoney() の実行部分は、ボタンのクリックイベントに直書きしてありますので興味ある方はご覧ください。大した実装ではないことが確認いただけます。
このアプリケーションのナイス(死語?)なところは、実際にこのクラスライブラリを呼び出し、実行して結果を得ているところです。脳内テストとは精度が異なりますね。
こういうことをしておけば、先ほどのように「コードの品質どうよ?」と聞かれたときに、
「動かして試したさ。完璧よ」
と自信たっぷりに言えるようになりますよね?でもちょっと待ってください。「いや、確かに動かしてテストしたのはわかるけど、品質はわかんないよね?どういうケースを確認できてるの?結果はどうだったの?」なんて聞かれたら・・・
「いやだから、実際に動かしたさっ、どんなケースかって、考えられる全ケースをやったさっ」
「実施した手順と結果?残ってないけど、コーディングと同じくらいの時間かけていろんなパターンで試したさっ」
残念ですが、まだまだ説得力がないなぁと思いませんか?何をやったのか、その結果どうなったのか。それが仕様にマッチしていたのか。まだわからないですね。せっかくの努力(簡易アプリ作る努力、テストする努力)が報われていないように思ってしまいます。
では、すべての操作(入力項目)と結果(出力項目)をメモ書きして、この簡易アプリケーションと一緒に提出できたら、それは説得力がありますが、メモ書きしている時間とは結構かかるものです。人間がやることですからメモの取り忘れやミスがあるかもしれませんしね。
そして、もう一つ問題があります。「ちょっと仕様変わったから(もしくはバグがあって修正したから)、もう一回確認してね」と言われたら・・・もう一回全部確認するのか???ちょっとつらいですね。
ステップ5. 単体テスト機能を活用しよう
そこで単体テストフレームワークの出番です。簡単に言ってしまうと、ステップ4. で触れている簡易アプリを作成して実際に動かすという形のテストを記録に残る形で実践しようよということになります。
単体テストでは、プログラムのコードとしてテストを記述します。要するに簡易アプリケーションに記述したロジックと同じですね。そこで結果を検証するロジックも記述しておくことで、そのテスト(テストメソッド)を実行すると結果が即わかる仕組みができあがります。このプログラムで書いたテストのコード自体が、操作であり、テスト対象のメソッドの仕様(の一部)でもあるわけですね。それと結果が記録できるというところが自動化されると上述のような「ちょっと足りない感」が解決できませんか?
なんか能書きが多くなってきてしまいましたが(^^; そこで、BaknAccount.Test プロジェクト内の BaknAccountTest.cs を開いてみてください。
[TestClass()]
public class BaknAccountTest
というクラスがあるのが確認できます。これが単体テスト用のテストクラスです。そしていくつ記述がありますが(今回は詳細説明は省きます)、
[TestMethod()]
public void DepositMoneyのテスト_通常ケース()
というメソッドがこのテストクラス内に存在しています。これが BankAccount クラスのDepositMoney() のテストケースの一つとなります。
見ていただくと、BankAccount のオブジェクト target を生成しているのがわかります。預金残高の初期値を 500.00F に設定しています。次に depositAmount という変数に DepositMoney() に渡す引数の数値が設定されています(10.55F)。要するに預金する額ですね。
そして、expected というところで、期待値を決めています。預金処理後の預金残高ですね(510.55F)。
そして、target.DepositMoney(dipositAmount) を実行しています。
要するに BankAccount クラスの外から、オブジェクトを生成して、DepositMoney() を実行している・・・ただそれだけなんですね。簡易 GUI とやっていることは一緒です。
そして、最後に結果の評価をしています。 Assert.AreEqual() というところです。第一引数と第二引数の値が一致していたらテスト成功、一致していなかったらテスト失敗とし第三引数のメッセージを表示するという形式です。
この特別な属性([TestMethod()])のついたメソッドは単体テスト機能によって実行することができ、その結果はテスト結果として記録される仕組みになっています。
要するに即実行し、即結果を得ることができ、それらはすべて記録として残すことができるわけです。
さらによいのは、テストをプログラムのコードとして記述しているので、いつでも実行可能(テスト実施可能)なわけですね。ここまで揃えば、「コードの品質どうよ?」と聞かれたら、
「テストケースは、ここにテストメソッドとして全部あるよ。結果も残ってるよ。」と自信をもっていえますね。せっかくの努力ですから残る形にしましょう!
さらに「ん?信用できないの?だったらもう一回全部実行してみれば?全部成功するからさっ、あとはテストケースに不足がないかチェックしてよ」なんて言えますね(^^)
一回ですべて書こうと思ったのですが、長くなったので、二回に分けることにしました。TDD の話は、第二回にずれこんでしまいましたね(^^;
ながさわともはる
Comments
Anonymous
January 21, 2008
PingBack from http://blogs.msdn.com/tomohn/archive/2008/01/22/team-system-tdd-2.aspxAnonymous
April 16, 2008
こんにちは、2日間にわたり皆さんのご参加で非常に “熱い” イベントとなった the Microsoft Conference 2008 (東京会場)、無事に走り抜けることができました。ご参加いただいた方、残念ながらご参加いただけなかった方も含め、皆さん、本当にありがとうございました。