簡単なサンプルを使ってインテリテストを体感する
では早速インテリテストがどんなテストを生成してくれるのか、試してみましょう。今回は、FizzBuzz(フィズ・バズ)問題と呼ばれるシンプルなコードを対象にします。FizzBuzz問題とは、1から100までの整数について、以下の文字列を出力するコードを記述する問題です。
- 3の倍数の場合は"Fizz"を出力
- 5の倍数の場合は"Buzz"を出力
- 3の倍数でかつ5の倍数の場合は"FizzBuzz"を出力
- それ以外の場合はその数値をそのまま文字列として出力
とてもシンプルですが、条件分岐などのプログラミング要素が含められているため、しばしばサンプルとして用いられる問題です。今回はFizzBuzz問題のうち、整数値を受け取って、上記の文字列を返す部分をメソッドとして実装し、インテリテストを試してみましょう。
Visual Studio 2015 Enterpriseを起動して、[新しいプロジェクト]をクリックし、[Visual C#]-[コンソール アプリケーション]を選択して新しいプロジェクトを作成します。作成したプロジェクトに新たなクラスとして「FizzBuzz.cs」を追加し、リスト1のようなコードを入力します。
public class FizzBuzz { public string Say(int i) { //3の倍数でかつ5の倍数の場合は"FizzBuzz"を出力 if (i % 3 == 0 && i % 5 == 0) return "FizzBuzz"; //3の倍数の倍数の場合は"Fizz"を出力 if (i % 3 == 0) return "Fizz"; //5の倍数の場合は"Buzz"を出力 if (i % 5 == 0) return "Buzz"; //それ以外の場合はその数値をそのまま文字列として出力 return i.ToString(); } }
コード自体は前述の定義通りなので解説は省略します。なお、インテリテストの対象となるクラスはpublicでなければなりませんので注意してください。例えば、自動的に生成された「Program.cs」のProgramクラスはpublicと宣言されていないので、そのままではインテリテストをかけることができません。テスト対象のメソッドについては、privateなメソッドやstaticなメソッドなども含め、特に条件がないようです。
では、このメソッドに対してインテリテストをかけてみましょう。メソッド上で右クリックしてコンテキストメニューを出し、[Run InteliTest]を実行します(図6)。
少し時間がたった後、画面下の「InteliTest Exploration Results」ペインに図7のような、インテリテストの実行結果が表示されます。注目したいのは表の中の「i」列と「結果」列です。Sayメソッド引数の「int i」に「i」列の値を指定すると、メソッドの戻り値として「結果」列の値が返る、という意味になります。
生成されたテストの結果を確認してみると、さまざまな入力値がテストされていることが分かりますが、いくらか想定外の結果も出ています。3の倍数("Fizz"を出力)、5の倍数("Buzz"を出力)、3でも5でも割り切れない場合(数値をそのまま出力)は想定通りの出力が出ていますが、3の倍数かつ5の倍数("FizzBuzz"を出力)のパターンについては、入力値が0となっています。これは、元のFizzBuzz問題では「1から100までの整数について」とあったのに対し、今回のメソッドでは入力値が範囲外の場合もそのまま処理していた、というのが原因です。
では、仕様を調整してコードを修正しましょう。今回は単純に「1から100の範囲外の値の場合はArgumentExceptionを例外送出する」という仕様にし、リスト2のコードをメソッド先頭に挿入しました。
//1-100範囲外の値の場合はArgumentExceptionを例外送出 if (i < 1 || i > 100) throw new ArgumentException();
コードを修正したところで、再度インテリテストを実行しましょう。先ほどと同じようにコンテキストメニューから実行しても良いですが、「InteliTest Exploration Results」ペインの[実行]ボタンを押すことでもインテリテストを再生成できます。今度は図8のように想定通りのテストが生成されます。
なお、インテリテストを実行するたびにテストが再生成されますが、生成されたテストを別のプロジェクトに保存する機能も存在しますので、同じテストに対して再帰テストを行うことも可能です。
ここでは、1から100の範囲外の数である0に対してはArgumentExceptionが例外送出されることが確認できます。そして、
- 3の倍数である96の場合:"Fizz"
- 5の倍数である50の場合:"Buzz"
- 3の倍数かつ5の倍数である60の場合:"FizzBuzz"
- それ以外の1、11の場合:そのままの数字
が、それぞれ結果として返ってくることが確認できます。このように、インテリテストを使うことで、テスト対象のメソッドのすべてのコードを網羅するテストが自動生成されます。
では、FizzBuzzコードをリスト3のように書いてしまうとどうなるでしょうか。
public string Say(int i) { //1-100範囲外の値の場合はArgumentExceptionを例外送出 if (i < 1 || i > 100) throw new ArgumentException(); //3の倍数の倍数の場合は"Fizz"を出力 if (i % 3 == 0) return "Fizz"; //5の倍数の場合は"Buzz"を出力 if (i % 5 == 0) return "Buzz"; //3の倍数でかつ5の倍数の場合は"FizzBuzz"を出力 //この順番ではこのコードは実行されない!!! if (i % 3 == 0 && i % 5 == 0) return "FizzBuzz"; //それ以外の場合はその数値をそのまま文字列として出力 return i.ToString(); }
これは仕様通りの順番でコードを記述した結果、"FizzBuzz"を出力する3と5の両方の倍数のパターンが、先に3の倍数である"Fizz"を出力するパターンに飲み込まれて実行されないコードができてしまう、というよくあるミスです。このコードに対してインテリテストをかけた結果は図9のようになります。
テスト自体は問題なく生成されていますが、上の緑のゲージに注目すると、ゲージが満タンになっていないことが確認できます。ゲージの上にマウスオーバーすると図10のようなメッセージが表示され、テストでカバーされていないコードが存在することが分かります。
ゲージの右横の文字列の部分は動的カバレッジ(テスト対象コードのうち、テストでカバーされている網羅率)を表しており、「13/15 ブロック」となっていますので、やはりテストでカバーされていない部分が存在することが分かります。今回は比較的簡単なミスでしたので、インテリテストがなくてもすぐに気づける部分だったかもしれません。しかし、複雑な業務コードにおいて、テストコードの作成が不十分であると、このようなバグになかなか気づくことができない場合がありますので、インテリテストのサポートは有用です。