対象読者
- テスト開発に興味はありながらも、テストファーストとまでは思い切れない方
- ドキュメントもコメントもないプログラムの保守を突然任されて頭を悩まされている開発者
必要な環境
本連載では以下の環境でサンプルを実行します。
- Windows 7(8、8.1も対応)
- Visual Studio 2015 RC Enterprise(一部機能はVisual Studio 2015 RCでも使用可能)
インテリテストとは
Visual Studio 2015には、Android/iOS開発や、さまざまなエディションのWindows上で動作するUWP(Universal Windows Platform)アプリのサポートなど、多彩な新機能が搭載されていますが、基本的なコーディングを支えるサポート機能も充実しています。本記事では、Visual Studio 2015 Enterpriseに新たに搭載されたインテリテスト(旧名:スマートユニットテスト)機能について解説します。
プログラミングにおいて、実装したコードが設計通りに正しく動作するかを検証するためのテストは非常に重要です。最近はテストファースト(Test-First)と呼ばれる「実際に動作するコードを書く前にテストを書いてテストを失敗させ、そのテストが正しく動作するようにコードを実装する」という開発手法も提唱されており、テストの重要度は高まっていると言えます。
とはいえ、すべての要求仕様を満たすテストを漏れなく記述するというのはなかなか骨の折れる仕事で、ある程度思いついたパターンでテストを書いて終わりにしてしまう、ということもあるかもしれません。そうすると、実装したコードのうち、特定の条件でしか実行されない部分がテストで網羅されず、潜在的なバグを取りこぼしてしまう、といった危険性も出てきます。
Visual Studio 2015 Enterpriseの新機能であるインテリテストを使えば、実装したコードのすべての行を通過するようなテスト群を自動生成してくれます。インテリテストはMicrosoft Researchで研究されていたPexというツールの発展形で、コードを分析しテストを自動生成する機能を持っています。
簡単なサンプルを使ってインテリテストを体感する
では早速インテリテストがどんなテストを生成してくれるのか、試してみましょう。今回は、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 ブロック」となっていますので、やはりテストでカバーされていない部分が存在することが分かります。今回は比較的簡単なミスでしたので、インテリテストがなくてもすぐに気づける部分だったかもしれません。しかし、複雑な業務コードにおいて、テストコードの作成が不十分であると、このようなバグになかなか気づくことができない場合がありますので、インテリテストのサポートは有用です。
Visual Studio 2015に搭載されたその他のデバッグ便利機能
インテリテスト以外のデバッグ用の新機能についても解説しておきます。なお、ここで解説する2点はEnterprise版だけでなく、すべてのVisual Studio 2015で使用できる機能です。
デバッグ中のラムダ式の評価
1つの改善点は、デバッグ中のラムダ式の評価が可能になったことです。Visual Studio 2013では、デバッグ中にラムダ式を評価することができなかったため、データベースアクセスにEntity Frameworkを使用したプロジェクトのデバッグで、取得しているデータについてラムダ式を使った処理を行いたいケースなどに対応することができませんでした。
Visual Studio 2015では、デバッグ中にウォッチ式やイミディエイト ウィンドウにラムダ式を使うことができますので、デバッグ時の自由度が大きく上がったといえるでしょう。例えばリスト4のようなコードの評価を試してみましょう。
//以下のコードがプログラム本体に書かれているとする var list = new string[] { "土井", "中村", "山田", "竹ノ内", "佐々木" }; ↓ //デバッグ中に以下のコードをイミディエイト ウィンドウやウォッチ式で使用できる //(1)文字列配列から、ラムダ式を使ったWhereメソッドやOrderByメソッドでデータを抽出する list.Where(x => x.Length==3).OrderBy(x => x) //(2)LINQのクエリ式も問題なく使用可能 from e in list where e.Length == 3 orderby e select e
文字列配列listに対して「名前が3文字のものを名前順に並び替えて取得する」という処理を、(1)ではLINQメソッド式で、(2)ではLINQクエリ式を使って記述しています。ウォッチ式で使用すると図1のようになります。
同じく、イミディエイト ウィンドウで使用した例は図2のようになります。
派手な機能ではありませんが、C#において重要な機能であるラムダ式を、デバッグ中にも自由に使えるようになったことは重要な改善点といえるでしょう。
ブレークポイント設定の簡便化
もう一つの改善点は、ブレークポイント設定がダイアログを開くことなく、コードペイン上ですぐに行えるようになったことです。ブレークポイントを設定し、赤丸アイコン上でマウスオーバーすると、図3のように2つのアイコンが表示されます。
左側の歯車アイコンはブレークポイントの設定、右側の丸が重なったアイコンは、ブレークポイントの有効/無効切り替え機能です。歯車アイコンをクリックすると、図4のようにブレークポイントの設定がコードペイン上に表示され、その場で設定を行えます。
例えば、図5は、ブレーク条件を「ヒットカウントが10の倍数」とし、10回ごとにブレークするよう設定した例です。
ブレークポイントの条件設定などは以前から搭載されている機能ですが、別途ダイアログを開くことなく、その場で設定できるというのは、なかなか便利な機能なのでご活用ください。
まとめ:インテリテストの効能を考える
本記事では、Visual Studio 2015 Enterpriseの新機能であるインテリテストを使い、テストコードを自動生成する方法について解説しました。インテリテスト機能を使えば、カバレッジ100%(コードに問題があって100%にならない場合は最大限のカバレッジ)になるようなテストを自動的に生成することができます。
生成されたテストはプログラムの外部仕様を表しますので、冒頭の対象読者に挙げた「ドキュメントもコメントもないプログラムの保守を突然任された」というケースでも、まずはそのプログラムに対してインテリテストをかけてみれば、「このコードは実際にどんな入力値に対してどんな出力を返すのか」という仕様については大まかに把握することができますので、プログラムを読み解くための手がかりとなるでしょう。また、先ほどのFizzBuzz問題を解くコードでも分かったとおり、外部仕様の漏れ(先ほどの例では入力値の範囲が1から100を外れていたケース)があった場合に、それに気づける可能性もあります。
このようにインテリテストは有用なツールではありますが、もちろん万能の「銀の弾丸」というわけではありません。インテリテストが行っているのは、コードを解析してカバレッジが最大になるような入力値と出力値の組み合わせを出力しているだけで、プログラマの意図を読み取ってくれるわけではありません。インテリテストの結果は「今そこにあるコードの入出力の組み合わせ」であり、それが「このコードが示すべき振る舞いを適切に表しているかどうか」を判定するのはプログラマの責任です。
それでも、煩雑な作業をある程度代行し、人間では見落としてしまいがちなケースでもカバレッジが最大になるようなテストをできるだけ出力してくれるインテリテストは、人間のミスを補い、コード品質を向上させるための頼もしいサポーターとなってくれるでしょう。ぜひ、Visual Studio 2015 Enterpriseのインテリテストのパワーを体感してみてください。
なお、Visual Studio 2015はRC版のため利用期間が限定されているものの、実際のアプリケーション開発が可能なGo-Liveライセンスが付与されています。実アプリケーションの開発にもご活用ください。