VisualStudio2008 professionalからIDE標準で単体テスト機能(ブラックボックステスト)が付いているので使ってみた。
俺は今まで単体テストはRSpecぐらいしかやったことがなかったし、何かライブラリを作った時はそれを使うGUI付きサンプルアプリケーションを作ることでしか動作確認してこなかったけど、これは次回からどんどん使っていく事にした。
この種のテストをブラックボックステストと呼ぶらしい。テスト対象のクラスの関数1つずつをブラックボックスと見るテストで、入出力の値に注目する。ある値を関数に入れてみて、予想した値が返ってくるかチェックすることで、関数が仕様を満たしているかチェックする。
今回はBenchmarkCounter.NETの中の処理時間を計るためのOrg.Shokai.Util.Benchmark.Counterクラスのテストを作った。Startメソッドで計測開始し、Stopメソッドで停止、その間の処理時間をミリ秒で保存できる。Start/Stopを複数回行った場合、その間の平均処理時間も求める事が出来る。
テストでは、出力値が正しい値になっているかどうかチェックする。このBenchmarkCounterのテストでは
- 処理時間を取得するメソッドの場合
→ 時間の値が0以上でなければエラー - 計測回数を取得するメソッドの場合
→ 何回か計測してみて、計測回数が合わなければエラー - 計測中かどうか?フラグの場合
→ Startメソッドにfalseだったらエラー
→ Stopメソッド後にtrueだったらエラー
■テストする
その前に
Visual Studio 2008単体テスト機能のすべて − @IT
この記事が詳しい。4ページ目に値を比較するAssertの関数の説明もある。
こちらのテストサンプルプロジェクトも参考にさせてもらった。
長沢智治のライフサイクルブログ : MSDN オフラインセミナー<全国ツアー|チーム開発編> 2007 Winter フォローアップページ
ソリューションを右クリックして新しいプロジェクトを追加する。「テスト プロジェクト」というのがある
Benchmarkプロジェクトのテストなので、BenchmarkTestという名前にした。
テストプロジェクトを右クリックしてテストクラスを追加する。
(元からあるテストクラス使わないので削除してよい)
「単体テストウィザード」を選択
現在あるメソッドのテストを一括して作成できる。
とりあえず全メソッド選択して作ってしまう
ちなみに、関数を右クリックしてテストを作る事もできる。
テストはクラス毎に1つのテストクラスになるので、右クリックから行った場合は既にあるテストクラスの一番下に追加される。
テストプロジェクトを右クリックして「スタートアッププロジェクトに設定」する
これでこのテストプロジェクト内のテストが行える。
とりあえず、動かしてみる。
F5もしくはツールメニューから「デバッグ開始」すればテストプロジェクトが走り、テスト結果が表示される。
まだテストコードを書いていないので全て「結果を作成できません」になる。
ある程度テストコードを書いていくと、こんな風に失敗したり成功したりする。
■テストコードの書き方
例として、Countingアクセサ(実体は関数)のテストを書く。計測中かどうかを表す関数で、Startして計測中はtrueになり、Stopするとfalseになる。この切り替えがちゃんと動いているかを確認したい。
Testを作成した直後はこうなっているはず
/// <summary>
///Counting のテスト
///</summary>
[TestMethod()]
public void CountingTest()
{
Counter target = new Counter(); // TODO: 適切な値に初期化してください
bool actual;
actual = target.Counting;
Assert.Inconclusive("このテストメソッドの正確性を確認します。");
}
このまま実行すると「このテストメソッドの正確性を確認します」と出るが、これは「これから私がテストコードを書き直して確認します」という意味なので削除して、自分で書く。
2種類チェックしたいのでCountingTest2も作った。手書きでテストを追加してもちゃんとテスト実行時に呼び出される。
[TestMethod()]
public void CountingTest()
{
Counter target = new Counter(); // TODO: 適切な値に初期化してください
target.Start();
target.Stop();
target.Start();
bool actual = target.Counting;
Assert.IsTrue(actual, "計測中フラグが立っていません");
}
[TestMethod()]
public void CountingTest2()
{
Counter target = new Counter();
target.Start();
target.Stop();
bool actual = target.Counting;
Assert.IsFalse(actual, "計測中フラグが消えていません");
}
Assert.条件(値1, “エラーメッセージ”);や
Assert.条件(値1, 値2, “エラーメッセージ”); という風に書く。
Assert条件文の最後の引数がエラーメッセージなのがちょっとわかりにくいが
Assert.AreEqual(a, b, “error! a != b”);
の場合、aとbが同じかどうか確かめてみて、違ったらerror!と表示する。
他にも色々条件式があってこの記事の下の方で解説されている
全部テストが通るとこうなる。
チェック項目が2つ以上ある関数の場合、1つのテスト関数で複数回Assertする事も出来るが、1つのテスト関数につき1つAssertする事にした。こうしておいた方がどちらの条件でエラーがでたのかわかりやすい為。
今回は先に出来ていたクラスのテストを後から作ったが、次は先にテストを作成して後から実装するテスト駆動開発でやりたい。先にテストを書き、Assertのエラー文を
Assert.IsTrue(actual, "Startメソッドで実行した後はtrueになるべきです");
のように書けば、これがそのまま詳細設計になる。