ASP.net MVCでは単体テスト(ユニットテスト)が自動化できてとてもラクです。
ちょこちょこ仕様書項目作って目視でだらだらやるよりは、多少手間がかかってもテストコード書いて自動化するほうが大局的にメリットがあります。
同じようなテスト内容はルーティン化できるのでいうほど大変ではありませんし、入力値を細かく制御したりできます。
ただ、もともとのコントローラやモデルの作りを、「ユニットテストやるぞ~」という状態にしておかないといけません。チームのコーディング規約的な問題ですね。
いままではcakephpとかで自動化したことはありましたが、MVCでは初めてです。
特にむつかしくはないですが、DB接続やセッションが一工夫必要となります。
単テなんてメカがやればいいのさ。
自動化最高。
人類はラクをするために文明を発展してきました。
機械学習、人工知能、アモーレ!
…
さて、ググってもプロジェクト作成時にチェックしておくパターンしかなかったので勝手にいろいろな情報を適時参考にしながらやります。
※↑これをチェックしておくとテストプロジェクトも自動的に作成
私はプロジェクト作成時にチェックしておかなかったので、
ソリューションに追加→テスト→単体テストプロジェクト
で追加しました。
なお、ユニットテストの自動化を目指しますが、微妙な検索結果のチェックとかDB取得の値とかの確認はやりません。基本的にテストプロジェクトから対象プロジェクトのDBテーブルにアクセスすることができないからです。※強引にできるようですが、テスト自動化の目的があいまいになるので
とりあえず面倒くさいことは機械にやらせればいいのさ。しかし準備は人間がやるんだぜ…。
テストを実装
既存のコントローラのアクションに対しユニットテストを行う想定とします。
テストプロジェクトにテストクラスを作成
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SampleProject.Controllers;
using SampleProject.Models;
using System.Web.Mvc;
using Moq; //①モッククラス
namespace SampleProjectUnitTest
{
[TestClass]
public class SampleControllerTest
{
// テストアクション
[TestMethod]
public void Index()
{
//②テスト処理
}
}
}
①…「Moq」というダミーのモックアップインスタンスを作成するライブラリです。事前にインストールしておきます。
Nugetコンソールから > Install-Package Moq
元のコントローラのアクション内でユーザ情報をセッションで管理しているので、Mockで補うことにします。
②…具体的なテストの処理を記述します。→後述します。
コントローラのDB処理(Modelでもロジック)は依存性を持たせないといけません。リポジトリクラスを使ってテスト用にモデルクラスをDIできるようにします。
DI用インターフェース定義
// DI用のインターフェースを定義、それを実装したリポジトリクラスで実装
public interface ISampleRepository
{
// 検索結果を返すメソッド(既存のビューモデルクラスを返却)
InputViewModel SearchResult(InputViewModel model, string user_id);
}
// 実装。本処理用
public class SampleRepository : ISampleRepository
{
//ユーザIDで検索する
public InputViewModel SearchResult(InputViewModel model string user_id)
{
//処理の実装。検索結果をモデルにセットする…
・・・・・
return model;
}
}
テスト用リポジトリモデルクラス
// テスト用、Model/UT/に作成
public class UTSampleRepository : ISampleRepository
{
public InputViewModel SampleResult(InputViewModel model, string user_id)
{
// 何もせず返却
return model;
}
}
対象のコントローラのコンストラクタでインジェクションできるようにする。
// コンストラクタでインジェクションするように
public class SampleController : Controller
{ // リポジトリオブジェクト
private ISampleRepository repository { get; set; }
//コンストラクタ
public SampleController()
{
// リポジトリを新規作成
this.repository = new SampleRepository();
}
//ユニットテストDI用コンストラクタ
public SampleController(ISampleRepository repo)
{
this.repository = repo;
}
// 検索アクション。リポジトリのロジックを呼び出す
public ActionResult Index(InputViewModel model)
{
// ユーザIDをセッションより取得
string user_id = (Session["UserInformations"] as UserInfo).Id;
// 検索結果を取得
model = this.repository.SearchResult(model, user_id);
return View(model);
}
リポジトリクラスをDIできる準備ができたので具体的なテストコードを以下のようになります。
②の処理抜粋
// テストメソッド
[TestMethod]
public void Index()
{
// DBデータ代用のテスト用リポジトリクラス
var repository = new UTSampleRepository();
// コントローラ作成
var controller = new SampleController(repository);
// 引数となるモデル
var model = new InputViewModel();
// 対象コンテキストのモック
var moq_context = new Mock<ControllerContext>();
// テスト用のユーザ情報を初期化
var userinfo = new UserInfo()
{
Id = "test",
Pass = ""
};
// セッションの戻り値をセットする→ユーザIDはセッション保持なのでセッションのモック作成
moq_context.SetupGet(p => p.HttpContext.Session["UserInformations"]).Returns(this.userinfo);
controller.ControllerContext = moq_context.Object;
// 結果を取得
var result = controller.Index(model) as ViewResult;
// 結果を検証(アサート)
Assert.IsNotNull(result);
}
テストメソッド上で右クリック→[テストの実行]
テスト成功!
まとめ
ユニットテストを自動化するぞ~というときは、常にDIを意識しないといけません。
DB処理などのロジックは分離して適時インジェクションするようにします。なるべく処理を依存する外部クラスは分離しましょう。
Moqクラスはいろいろ使えそうですが、既存メソッドをオーバーライドするのでいまのところは「ControllerContext」のモックに制限かな。※プロパティやメソッドはvirtualで記述
単体テストを意識すればコードの質も保てる気がします、かな?