単体テスト基本のキ
さて、今回紹介する単体テストですが、実際に活用しているでしょうか?
JavaやLL界隈ではTDD(テスト駆動開発)という言葉が当たり前のように使われ、実際にそれに沿って開発が進められていることが多いと言えます。
では、.NET界隈ではどうでしょう。
あくまで著者の認識ですが、以下の程度の広がり具合だと感じています。
- コミュニティ活動などを頻繁に実施している方々
- アジャイル開発などを実践されている方々または、少数メンバーの開発チーム
つまり、コードの単体テストというより、アプリを実行して目視によるテストを行うだけというパターンが多く、単体テストプロジェクトを利用してテストを行うことは.NET界隈ではまだ大勢ではないと感じています(実際、.NETにおけるTDDや単体テストに関する言及は他の言語に比べると圧倒的に少ないです)。特にVB6から開発を実施して.NETに移行している開発者や開発チームはその傾向が強いのではないでしょうか。
この原因として、そもそも単体テストを行う文化が根付いていない、なぜ単体テストをするのかよく分からない、何をすればいいのかよく分からない、などがあると思います。今回は実際に単体テストを活用している方にとっては初歩過ぎる部分ですが、まずは単体テストをおさらいする意味で基本から紹介してみたいと思います。
そもそも単体テストとは?
単体テストとは、クラスやメソッドなど小さな範囲に限定して開発者の理想通りに動作するか動作検証を行うテスト手法のことです。単体テストはアサートメソッドを利用します。アサートメソッドは1つのメソッドをコールして、理想値(expected)と実際の値(actual)が同一になるかどうかなどをテストするメソッドです。
もし理想通りの動作をしない場合は、テストを実施しているクラスやメソッドの修正を行い、テストを通すようにします。単体テストを実施することで、バグを早期発見ならびに修正できます。
また、単体テストコードが存在することは、そのまま動くロジックのサンプルコードの検証コードが存在しているということを意味しています。開発担当者が変わった場合や、ロジックを拡張してアプリケーションをエンハンス(注1)する際にも、容易に検証箇所の見極めやエンハンス箇所の特定が行えます。メインロジックのコード品質も向上すると言えるでしょう。
例えばCSVファイルのみ対応のアプリをExcelも対応するなど。
それでは、.NETにおける単体テストを紹介します。
.NETにおける単体テスト
VS 2008以降、Professional Edition以上のバージョンでは単体テスト用のフレームワークとして、単体テストプロジェクト(MSTest)が利用できます。もちろんVS 2010上でも同様です。IDEに標準で付いていることもあり、昨今ではMSTestを利用することが.NETアプリケーションにおける単体テストのデファクトスタンダードとなりつつあります。MSTestが主流になる以前はNUnitなどを利用して単体テストを実施してきました。
実際のテストプロジェクトも確認してみましょう。ASP.NET MVCプロジェクトを作成する際にダイアログで[単体テスト プロジェクトを作成する]チェックボックスにチェックを付けると、自動でテストプロジェクトとHomeControllerクラスに対するテストメソッドが生成されます(図1)。
なお、HomeControllerクラスに対する既定のテストメソッドは以下のとおりです。
public class HomeControllerTest { [TestMethod] public void Index() { // 準備 HomeController controller = new HomeController(); // 実行 ViewResult result = controller.Index() as ViewResult; // アサート Assert.AreEqual("ASP.NET MVC へようこそ", result.ViewBag.Message); } [TestMethod] public void About() { // 準備 HomeController controller = new HomeController(); // 実行 ViewResult result = controller.About() as ViewResult; // アサート Assert.IsNotNull(result); } }
まず、注目して欲しいのはコメント部分です。単体テストは大別すると、3ステップで形成されています。
ステップ | 概要 |
準備 | テストを実行するためのオブジェクトなどの環境の用意 |
実行 | オブジェクトに対してテスト処理を実行 |
アサート | テストの実行結果と理想値による検証を実施 |
準備で利用するのは通常のプロジェクトで記述したクラスのオブジェクトやモックオブジェクトになります(モックオブジェクトについては後述します)。
実行では、準備のステップで作成したオブジェクトに対してテストしたいメソッド呼び出しを実施します。テストを通ったかどうかは戻り値で判断するため、テストしたいメソッドは戻り値を取得できるようにしておきましょう。
アサートでは、実行ステップで取得した実行結果と開発者の理想値とを比較し、検証を実施します。ここで理想値と実行結果が異なる場合はテストが失敗していることを示し、テストを行ったメソッドの修正が必要になります。
MVCプロジェクト作成時に既定で生成される上記テストコードはHomeControllerオブジェクトのIndexアクションメソッド、Aboutアクションメソッド内の処理を実行しています。メインロジックとなるIndexアクションメソッドでは、ViewBagのMessage変数に「ASP.NET MVC へようこそ」という値を設定し、アクションメソッドと同名のViewを返しています。Aboutアクションメソッドはアクションメソッドと同名のViewを返しているだけです。
Indexテストメソッド内では、ViewBagのMessage変数に対してメインロジックで設定した値と同じかどうかをAssertクラスのAreEqualメソッドを利用して検証します。
Aboutテストメソッド内では、特に処理は実施していません。しかし、戻り値としてNullではないことをIsNotNullメソッドを利用して検証しています(実際はビューが取得できるため、Nullではありません)。
もちろん一致している場合はテストは通りますし、不一致の場合、テストは通らず失敗します。
検証を実施するAssertクラスで提供されている代表的なメソッドは以下のとおりです(オーバーロードが多いためメジャーな定義だけ列挙します)。
メソッド名 | 概要 |
AreEqual(オブジェクト,オブジェクト) | 指定された2つのオブジェクトが同一かどうかを検証 |
AreNotEqual(オブジェクト,オブジェクト) | 指定された2つのオブジェクトが異なることを検証 |
AreSame(オブジェクト,オブジェクト) | 指定された2つのオブジェクトが同一のオブジェクトを参照しているか検証 |
AreNotSame(オブジェクト,オブジェクト) | 指定された2つのオブジェクトが異なるオブジェクトを参照しているか検証 |
IsNull(オブジェクト) | 指定されたオブジェクトがNullかどうかを検証 |
IsNotNull(オブジェクト) | 指定されたオブジェクトがNullではないことを検証 |
IsInstanceOfType(オブジェクト,型) | 指定されたオブジェクトが指定された型のオブジェクトかどうかを検証 |
IsNotInstanceOfType(オブジェクト,型) | 指定されたオブジェクトが指定された型のオブジェクトではないことを検証 |
IsTrue(bool) | 指定された条件がTrueかどうかを検証 |
IsFalse(bool) | 指定された条件がFalseかどうかを検証 |
ReferenceEquals(オブジェクト,オブジェクト) | 指定された2つのオブジェクトが |
上記メソッドを活用することで、基本的な単体テストの記述は実現できます。
ただし、MSTestだけではできる範囲が限定されます。例えば、未実装のクラスやメソッドに対して先にテストを実施したい場合、クラスのオブジェクトを生成するだけでは当然Exceptionが発生します。また、ModelやHTTP、セッションや認証系の情報を扱うオブジェクトを生成する場合、都度アプリケーションサーバーを起動したり、データベースへのアクセスを実施するのはテストを実施する本質から離れて手間ばかりが増えてしまいます。
この手間を解決する方法としてメソッドなどのインターフェースのみを実装したダミーのオブジェクトとなるモックを生成し、利用します。.NETでは、moqがメジャーなライブラリとして利用されています。
moqがメジャーなライブラリとして使用される理由として.NETユーザーにおなじみの強い型付けの利用が挙げられます。強い型付けが利用できるということは、モックオブジェクトでありながら実際のオブジェクト同様にインテリセンスが利用できるということです。
では、moqの活用方法についてご紹介します。