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


33

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

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パイプラインまたはサービスライブラリによって行われます。

したがって、尋ねる質問は次のようになります。

  • この方法を単体テストすることの価値は何ですか?
  • それが上の壊れていないだろうRequest.UserHostAddressModelStateとNullReferenceExceptionで?これらをモックする必要がありますか?
  • このメソッドを再利用可能な「ヘルパー」に屈折させると(おそらく、それを何回行うかを考える必要があります!)、実際にテストしているのはほとんどが「パイプライン」であり、おそらく、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);
}

明らかに私はこの誇張して無意味な例に鈍感ですが、ここに追加する知恵はありますか?

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


その特定のテストのRoI(投資収益率)は、無限の時間とお金がない限り、努力する価値はないと思います。壊れそうなものをチェックするためにケビンが指摘するテストを書いたり、自信を持って何かをリファクタリングしたり、エラーの伝播が期待どおりに行われていることを確認するのに役立ちます。必要に応じてパイプラインテストをよりグローバル/インフラストラクチャレベルで実行でき、個々のメソッドレベルではほとんど価値がありません。価値がないと言っているのではなく、「少し」です。それで、あなたの場合にそれが良いRoIを提供するなら、それのために行きなさい、さもなければ、より大きな魚を最初に捕まえてください!
Mrchief

回答:


18

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

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

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

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

NullReferenceExceptionのRequestとModelのチェックを指摘しましたが、ModelState.IsValidがModelのNullReferenceの処理を処理すると思います。

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


こんにちはケビン、答えてくれてありがとう。他の人が何か入って来るかどうかを確認するために、しばらくお任せしますが、これまでのところ、あなたのものが最も論理的で明確です。
LiverpoolsNumber9

気の利いた。喜んでくれました。
ケビン

3

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

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

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

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

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

これには多くのデューデリジェンスが必要ですが、価値があると思います。


答えてくれてありがとう まず、単体テストの「ヘルパーメソッド」はv.dangerousです。第二に、「私のリポジトリが呼び出されることをテストする」-依存性注入を経由するということですか?
LiverpoolsNumber9

なぜ便利な方法は危険なのでしょうか?私はBaseControllerTests彼ら全員が住んでいるクラスを持っています。リポジトリをモックアウトします。Ninjectを使用してプラグインします。
トラビスパーク

ヘルパーでエラーまたは誤った仮定をした場合はどうなりますか?私のもう1つのポイントは、統合テスト(つまり、エンドツーエンド)だけが、リポジトリが呼び出されるかどうかを「テスト」できるということでした。単体テストでは、リポジトリを手動で「新規作成」またはモックします。
LiverpoolsNumber9

リポジトリをコンストラクタに渡します。テスト中にモックします。モックが期待どおりに動作することを確認します。ヘルパーは単に解体ActionResult渡されたURLを、モデルなどを検査するS
トラヴィス公園

わかりました。「リポジトリが呼び出されることをテストする」という意味を少し誤解しました。
LiverpoolsNumber9

2

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

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

補足として。

また、リストを参照渡しする知恵に疑問を呈します(c#は参照で渡されますが、そうではない場合でも不要です)-エラーメッセージをポンプするためにサービスが使用できるerrorActionアクション(アクション)を渡す必要に応じて処理できます(リストに追加したい場合、モデルエラーを追加したい場合、ログに記録したい場合)。

あなたの例では:

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


言及する価値がある、これはあなたのサービスが同じアプリケーションに存在することを前提とします。
マイケル

サービスは別のdllにあります。しかし、とにかく、あなたはおそらく正しい「参照」です。他の点では、myServiceが例外をスローするかどうかは関係ありません。私はmyServiceをテストしていません-メソッドを個別にテストします。私は、(おそらく)模擬のmyServiceでActionResultの「ユニット」を純粋にテストすることについて話している。
LiverpoolsNumber9

サービスとコントローラーの間に1対1のマッピングがありますか?そうでない場合、一部のコントローラーは複数のサービス呼び出しを使用しますか?もしそうなら、それらの相互作用をテストできますか?
マイケル

いいえ。1日の終わりに、サービスメソッドは入力(通常はビューモデルまたは単なる文字列/整数)を受け取り、「処理」を行い、falseの場合はブール値/エラーを返します。コントローラーとサービス層の間に「直接」リンクはありません。は完全に分離されています。
リバプール

はい、私は理解しています、コントローラーとサービス層の間のリレーショナルモデルを理解しようとしています-各コントローラーに対応するサービスメソッドがないと仮定すると、一部のコントローラーが複数のサービス方法?
マイケル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.