web-dev-qa-db-ja.com

ASP.NET MVCでコントローラーを単体テストすることで実際の価値はありますか?

この質問は、しばらくの間私を悩ませてきた質問なので、いくつかの興味深い答えが出ることを願っています。

ASP.NET MVCでコントローラーをユニットテストする際に実際の値はありますか?

つまり、ほとんどの場合(そして私は天才ではありません)、私のコントローラーメソッドは、次のような最も複雑なものでさえもです。

public ActionResult Create(MyModel model)
{
    // start error list
    var errors = new List<string>();

    // check model state based on data annotations
    if(ModelState.IsValid)
    {
        // call a service method
        if(this._myService.CreateNew(model, Request.UserHostAddress, ref errors))
        {
            // all is well, data is saved, 
            // so tell the user they are brilliant
            return View("_Success");
        }
    }

    // add errors to model state
    errors.ForEach(e => ModelState.AddModelError("", e));

    // return view
    return View(model);
}

面倒な作業のほとんどは、MVCパイプラインまたはサービスライブラリのいずれかによって行われます。

だから多分尋ねるべき質問は次のようになるでしょう:

  • このメソッドを単体テストすることの価値は何でしょうか?
  • nullReferenceExceptionでRequest.UserHostAddressModelStateが壊れないでしょうか?これらをモックしようとする必要がありますか?
  • この方法を再利用可能な「ヘルパー」(おそらく何回行うかを考えると、これを行う必要があります)に屈折させる場合、実際にテストしているのがほとんどすべて「パイプライン」である場合、それは価値のあるテストです。おそらく、Microsoftによってその寿命の1インチ以内でテストされていますか?

私のポイントは本当にだと思います、以下を行うことは全く無意味で間違っているようです

[TestMethod]
public void Test_Home_Index()
{
    var controller = new HomeController();
    var expected = "Index";
    var actual = ((ViewResult)controller.Index()).ViewName;
    Assert.AreEqual(expected, actual);
}

明らかに、私はこの誇張された無意味な例に鈍感になっていますが、誰かがここに追加する知恵を持っていますか?

楽しみにしています...ありがとう。

33

非常に単純なものでも、単体テストは複数の目的に役立ちます

  1. 自信、書かれたことは期待される出力に準拠しています。正しいビューを返すことを確認するのは簡単なことのように思えるかもしれませんが、結果は、要件が満たされているという客観的な証拠です
  2. 回帰試験。 Createメソッドを変更する必要がある場合でも、期待される出力の単体テストが残っています。はい、出力は変化する可能性があり、その結果、テストは不安定になりますが、管理されていない変更管理に対するチェックです。

その特定のアクションについて、私は次のことをテストします

  1. _myServiceがnullの場合はどうなりますか?
  2. _myService.Createが例外をスローした場合はどうなりますか?処理するために特定の例外をスローしますか?
  3. 成功した_myService.Createは_Successビューを返しますか?
  4. エラーはModelStateまで伝播されますか?

NullReferenceExceptionのリクエストとモデルのチェックを指摘しましたが、ModelState.IsValidがモデルのNullReferenceの処理を担当すると思います。

リクエストをモックアウトすると、Nullリクエストを防ぐことができます。Nullリクエストは、本番環境では一般的に不可能ですが、単体テストでは発生する可能性があります。統合テストでは、さまざまなUserHostAddress値を提供できます(コントロールに関する限り、リクエストは依然としてユーザー入力であり、それに応じてテストする必要があります)。

18
Kevin

私のコントローラーも非常に小さいです。コントローラーの「ロジック」のほとんどは、フィルター属性(組み込みおよび手書き)を使用して処理されます。したがって、私のコントローラーには通常、ほんの少しのジョブしかありません:

  • HTTPクエリ文字列、フォーム値などからモデルを作成します。
  • 基本的な検証を実行する
  • データまたはビジネスレイヤーを呼び出す
  • ActionResultを生成する

ほとんどのモデルバインディングはASP.NET MVCによって自動的に行われます。 DataAnnotationsも、ほとんどの検証を処理します。

テストするものがほとんどなくても、私は通常それらを書きます。基本的に、私のリポジトリが呼び出され、正しいActionResultタイプが返されることをテストします。 ViewResultには、正しいビューパスが返され、ビューモデルが期待どおりに見えることを確認するための便利なメソッドがあります。正しいコントローラ/アクションがRedirectToActionResultに設定されていることを確認する別の方法があります。 JsonResultなどの他のテストがあります。

Controllerクラスをサブクラス化すると、残念なことに、内部でHttpContextを使用する便利なメソッドが多数提供されます。これにより、コントローラの単体テストが困難になります。このため、通常はHttpContext依存の呼び出しをインターフェイスの背後に配置し、そのインターフェイスをコントローラーのコンストラクターに渡します(私はNinject Web拡張機能を使用してコントローラーを作成しています)。このインターフェイスは通常、セッション、構成設定、IPrincipleおよびURLヘルパーにアクセスするためのヘルパープロパティを貼り付ける場所です。

これには相当の注意が必要ですが、それだけの価値があると思います。

3
Travis Parks

明らかに、いくつかのコントローラはそれよりもはるかに複雑ですが、純粋にあなたの例に基づいています:

MyServiceが例外をスローするとどうなりますか?

補足として。

また、参照によってリストを渡すことの知恵に疑問を投げかけます(とにかく、c#は参照によって渡されるので不要ですが、そうでなかったとしても)-サービスがエラーメッセージを送り出すために使用できるerrorActionアクション(アクション)を渡しますその後、必要に応じて処理できます(リストに追加したり、モデルエラーを追加したり、ログに記録したりできます)。

あなたの例では:

refエラーの代わりに、(string s)=> ModelState.AddModelError( ""、s)などを実行します。

2
Michael