リポジトリクラスの単体テスト
前回DI実装したサンプルでは、PubsRepositoryクラスでインターフェースを実装し、HomeControllerクラスやPubsControllerクラスにDIを実施しました(図5)。
DI実行前のSendMessageメソッドが理想通りの動作をするかテストを実施します。SendMessageメソッドは文字列内にメソッドにアクセスした日時を付加し、文字列として値を返すメソッドです。
実際のテストコードは以下のとおりです。
[TestMethod] public void SendMessageTest() { //// 準備 理想値の設定 string expected = String.Format("現在の日時は{0}です。これはDIの実装例です。", System.DateTime.Now.ToString()); string actual; PubsRepository repository = new PubsRepository(); //// 実行 SendMessageメソッドの実行 actual = repository.SendMessage(System.DateTime.Now.ToString()); //// アサート Assert.AreEqual(expected, actual); }
テストコードを見てお気づきになるかもしれませんが、実はDI実装しているSendMessageの単体テストは通常のメソッドの単体テストと変わりありません。
上記テストコードでは意識しづらいかもしれませんが、細分化されたメソッド単位でみると依存関係がないためテストがしやすくなります。
しかし、第4回で記載したようにDIは依存関係を分離していく過程でクラスやインターフェースが増大しプロジェクト全体が見渡し辛くなることもあります。依存関係の低下によるテストの容易さと、全体の見渡し辛さを比較した上でプロジェクトに合うほうを選択すると良いでしょう。
また、DI使用時のテストの留意点としてすべてのアプリケーションに共通して言えることですが、設計をしっかりするという一言に尽きます。
DIはやろうと思えば場凌ぎ的なパッチの要領でメソッドなどを付加していくことができてしまいます。このような形で追加していくことは結果としてテストを容易にできるメリットを打ち消してしまうので、留意しましょう。
ちょっとしたTipsになりますが、テストコードは自分以外の開発者が見ても、このテストは何をするロジックなのか? 何をインプットし、何がアウトプットされ、どうやって確認するのか? などの情報をテストコードだけで視認できるような記述を心がけることがコードの品質を向上させるポイントになります。例えば、上記準備ステップの段階で、理想値expectedに値を設定しています。actualは実行の段階で取得されますが、上記を見るだけで取得したい値は「今の日時情報が付加された文字列」ということ、文字列同士を比較したいテストだということも相手に伝えることができます。
テストコードはメインのロジック以上に可読性が要求されるため、相手に伝わる記述やコメントを意識しましょう。
上記テストコードは、最初に設定した文字列と、PubsRepositoryオブジェクトのSendMessageメソッドを実行した戻り値を比較しアサートを実行しています。
続いて、リポジトリパターンを適用しているPubsControllerクラスのテストも実施します。今回は新たにデータを作成するCreateメソッドのテストを紹介します。
最初にテストコードを確認してみましょう。
[TestMethod()] public void CreatePostTest() { // 準備 理想値やテストデータや値や各コントローラーの生成 int expected = 11; // 10件のテストデータを作成 var testData = FakePubsData.CreateTestData(); var repository = new PubsRepositoryTest(testData); var controller = new PubsController(repository); // 11件目のデータの用意 titles SampleTitles = new titles() { title_id = "CreateTest", advance = 1000, title = "Sample Book Create", royalty = 10, price = 30, pubdate = DateTime.Now.AddDays(1), notes = "追加!", type = "Fake", }; // 実行 11件目のデータを作成 var actual = controller.Create(SampleTitles); // アサート Assert.AreEqual(expected, repository.FindAllTitles().Count()); Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult)); }
テストコード自体は特別なことはしていません。上記テストコードのポイントはダミーオブジェクト差し替えの部分です。
CreatePostTestメソッドでは、Createメソッドに値が保存されるかをテストするために正常に動作した場合の理想値11を設定し、その後ダミーデータを作成するFakePubsDataクラスのCreateTestDataメソッドを利用し、10件のデータを生成します。続いて11件目のデータを用意し、Createメソッド実行後にデータが11件に増えているかどうかをアサートでチェックしています。
コントローラーの単体テスト
最後にHomeControllerクラスとPubsControllerクラスそれぞれ単体テストを記載します。
HomeControllerクラスのIndexアクションメソッドにDIを利用してPubsRepositoryオブジェクトのSendMessageメソッドを実装しています。SendMessageメソッドの戻り値はViewBag.SendMessageに対して設定しているので、ViewBagの値に対してテストを実施してみます。
[TestMethod] public void Index() { //// 準備 理想値の設定 string expected = String.Format("現在の日時は{0}です。これはDIの実装例です。", System.DateTime.Now.ToString()); string actual; var TestData = FakePubsData.CreateTestData(); PubsRepositoryTest repository = new PubsRepositoryTest(TestData); ISendMessage im = repository; HomeController controller = new HomeController(im); // 実行 Indexアクションメソッドの呼び出しと実行結果の設定 ViewResult result = controller.Index() as ViewResult; actual = result.ViewBag.SendMessage; // アサート Assert.AreEqual(expected, actual); }
基本的にPubsRepositoryオブジェクトのSendMessageメソッドのテストコードと同じです。違いはViewBag.SendMessageオブジェクトの値をアサート対象としているかどうかになります。単体テストは可能な限りアサートの範囲や数を絞ることを心がけると良いでしょう。上記テストコードは他に変数resultがnullかどうかアサートするなどの記述もできます。ただ、フォーカスを絞ることで、より可読性が向上し、メインロジックもすっきりしたものになるでしょう。もちろんこれはケースバイケースなのでURLルーティングのテストで記載したように確かめたいことに対して複数のアサートを記述するなども実際には多分にあります。
なお、コントローラーはHttpContextに依存してしまうことがあります。認証や、ViewBag/ViewDataの利用、Session情報を知りたい場合はこれらのモックを作成する必要もあります。
基本的に利用方法は前回解説したモックオブジェクトの生成方法と変わりありません。
// Controllerのモックオブジェクト生成例 Mock<ControllerContext> mock = new Mock<ControllerContext>(); mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns("Naokio"); mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
上記では、ユーザー認証のフィルターが正確に動作しているかどうかテストしたい場合に記述するコードです。Naokioというユーザー名を取得できるモックオブジェクトを生成し、リクエストが認証されているかBool型で示すIsAuthenticatedプロパティの値をTrueに設定しています。
なお、コントローラーでHttpContextを扱うテストを実施したい場合はURLルーティングのテスト同様にHttpContextBaseクラスのモックオブジェクトを生成しテストすると良いでしょう。
まとめ
今回はMVC 3のサンプルプロジェクトに対する単体テストの記述方法を単体テストを利用する上でのTipsを織り交ぜながら紹介しました。サンプルプロジェクトはシンプルなため、テストコードもシンプルになりましたが、実際の業務アプリケーションではより複雑なテストメソッドになるかと思います。
今回紹介した内容を積み重ねていくことで、テストの自動化やテストファーストとなるTDDなどの開発手法にも発展させることができます。先ずは基本となる単体テストの記述を継続して行うようにしていきましょう!
一年に渡り不定期で連載してきたASP.NET MVC 3の連載も今回で最終回です。すでにASP.NET MVC 4のBetaも公開されていますが、ASP.NET MVC自体は3の段階で開発手法やフレームワークとしての大枠は完成したと言える状態です。
ASP.NET MVC 4では以下のような機能が新たに追加されています。
-
新たなスクリプトファイルの追加
スマートデバイス対応のスクリプトライブラリjQuery MobileやJavaScriptでMVVMパターンを実現するknockout.jsなどの追加
-
ASP.NET Web API
RESTfulなWebサービス構築を実施するHTTPサービス構築用のフレームワーク
-
Simple Page Application
ASP.NET Web APIと複数のJavaScriptライブラリを駆使し、単一のページでCRUD操作すべてを実現できるWebアプリケーションを開発するフレームワークとテンプレート
-
Display Mode
複数のViewを用意し、リクエストのあったデバイスやブラウザに応じてViewを切り替える機能
他にもさまざまな機能が追加されています。ASP.NET MVC 4自身はモバイル対応やRESTfulなWebサービス構築をサポートするASP.NET Web APIテンプレートなど、ASP.NET MVCというより、MVC周りで活用できる技術やMVCの機能を活かして作れる簡単なアプリケーションテンプレートなどの機能強化がメインです。ASP.NET MVC 3で得た知識はASP.NET MVC 4でも活かせます。
また、ASP.NET MVCを活用したサンプルなども増えてきているので、さまざまなソースやテストコードを見て学習してみるとよいでしょう。