web-dev-qa-db-ja.com

1つのユニットで.NET MVCコントローラーをテストするにはどうすればよいですか?

.NET mvcコントローラーの効果的なユニットテストに関するアドバイスを探しています。

私が働いているところでは、そのようなテストの多くはmoqを使用してデータレイヤーをモックし、特定のデータレイヤーメソッドが呼び出されることを表明します。これは、APIをテストするのではなく、実装が変更されていないことを本質的に検証するため、私には有用ではないようです。

また、返されたビューモデルのタイプが正しいことを確認するなどのことを推奨する記事を読みました。ある程度の価値を提供していることがわかりますが、それだけでは、多くのモックコードを記述する努力に値しないようです(アプリケーションのデータモデルは非常に大きく複雑です)。

誰でもコントローラーユニットテストにいくつかのより良いアプローチを提案したり、上記のアプローチが有効/有用である理由を説明できますか?

ありがとう!

61
ChaseMedallion

コントローラーユニットテストでは、データレイヤーではなく、アクションメソッドでコードアルゴリズムをテストする必要があります。これが、これらのデータサービスをモックする1つの理由です。コントローラーは、リポジトリ/サービス/などから特定の値を受け取り、それらから異なる情報を受け取ると異なる動作をすることを期待しています。

ユニットテストを作成して、コントローラが非常に特定のシナリオ/状況で非常に特定の方法で動作することをアサートします。データレイヤーは、これらの状況をコントローラー/アクションメソッドに提供するアプリの一部です。コントローラーが別の場所から情報を取得していることを確信できるため、サービスメソッドがコントローラーによって呼び出されたことをアサートすることは有益です。

誤ったタイプのビューモデルが返された場合、MVCはランタイム例外をスローするため、返されたビューモデルのタイプを確認することは有益です。単体テストを実行することで、本番環境でこれが発生するのを防ぐことができます。テストが失敗すると、本番環境でビューが例外をスローする場合があります。

単体テストは、リファクタリングがはるかに簡単になるため、価値があります。実装を変更し、すべての単体テストに合格することで動作が同じであることを断言できます。

コメントへの回答#1

テスト対象のメソッドの実装を変更すると、下位層のモックメソッドの変更/削除が必要になる場合、ユニットテストも変更する必要があります。しかし、これはあなたが思うほど頻繁に起こるべきではありません。

典型的なred-green-refactorワークフローは、ユニットテストの作成を要求しますbeforeテストするメソッドの作成。 (これは短い時間でテストコードがコンパイルされないことを意味し、多くの若くて経験の浅い開発者が赤緑のリファクタリングを採用するのが難しい理由です。)

ユニットテストを最初に記述すると、コントローラーが下位層から情報を取得する必要があることがわかります。その情報を取得しようとしていることをどのように確認できますか?情報を提供する下位層メソッドをモックアウトし、下位層メソッドがコントローラーによって呼び出されることをアサートします。

「実装の変更」という用語を使用したときに、誤解を招く可能性があります。モックされたメソッドを変更または削除するためにコントローラーのアクションメソッドと対応する単体テストを変更する必要がある場合、コントローラーの動作を実際に変更しています。定義上、リファクタリングとは、全体的な動作と期待される結果を変更せずに実装を変更することを意味します。

Red-Green-Refactorは、コードのバグや欠陥が現れる前にそれを防ぐのに役立つ品質保証アプローチです。通常、開発者はバグを表示した後に実装を変更してバグを削除します。繰り返しになりますが、あなたが心配しているケースはあなたが思うほど頻繁に起こるべきではありません。

47
danludwig

まず、コントローラーをダイエットする必要があります。それから 楽しんでください それらを単体テストできます。それらが太っていて、すべてのビジネスロジックを詰め込んでいる場合、ユニットテストで人生をあざけるようなものを渡し、これが時間の無駄だと不平を言うことに同意します。

複雑なロジックについて話すとき、これは必ずしもこのロジックを異なるレイヤーに分離できず、各メソッドを単体で単体テストすることを意味するわけではありません。

27
Darin Dimitrov

はい、DBまでずっとテストする必要があります。モックにかける時間は短くなり、モックから得られる値も非常に少なくなります(システムで発生する可能性のあるエラーの80%は、モックによって選択することはできません)。

コントローラーからDBまたはWebサービスまでのすべての方法でテストする場合、単体テストではなく統合テストと呼ばれます。私は個人的には単体テストではなく統合テストを信じています(両者は異なる目的を果たしますが)。そして、統合テスト(シナリオテスト)を使用して、テスト駆動開発を正常に行うことができます。

これが私たちのチームにとっての仕組みです。最初のすべてのテストクラスは、DBを再生成し、最小限のデータセット(たとえば、ユーザーロール)をテーブルに追加/シードします。コントローラーの必要性に基づいて、データベースにデータを入力し、コントローラーがタスクを実行するかどうかを確認します。これは、他の方法で残されたDB破損データがテストに失敗しないように設計されています。実行にかかる時間を除いて、ユニットテストのほとんどすべての品質(理論であっても)は取得可能です。 コンテナを使用すると、順次実行にかかる時間を短縮できます。また、コンテナの場合、すべてのテストがコンテナ内の新しいDBを取得するため、DBを再作成する必要はありません(テスト後に削除されます)。

より現実的なデータソースを作成することができなかったため、モック/スタブを使用することを余儀なくされたとき、私のキャリアでは2%の状況しかありませんでした(または非常にまれです)。しかし、他のすべての状況では、統合テストが可能性でした。

このアプローチで成熟レベルに達するには時間がかかりました。テストデータの入力と取得(ファーストクラスの市民)を扱うNiceフレームワークがあります。そして、それは大きな時間を完済します!最初のステップは、モックと単体テストに別れを告げることです。モックが意味をなさない場合、それらはあなたのためではありません!統合テストはあなたに良い睡眠を与えます。

===================================

以下のコメントの後に編集:デモ

統合テストまたは機能テストは、DB /ソースを直接処理する必要があります。モックなし。これが手順です。テストしたいgetEmployee(emp_id)。以下のこれら5つのステップはすべて、単一のテストメソッドで実行されます。

  1. DBを削除
  2. DBを作成し、ロールおよびその他のインフラデータを設定します
  3. IDを持つ従業員レコードを作成する
  4. このIDを使用してgetEmployee(emp_id)//を呼び出します。これによりapi-url呼び出しが可能になります(そのため、テストプロジェクトでdb接続文字列を維持する必要がなく、変更するだけでほぼすべての環境をテストできます)ドメイン名)
  5. Assert()/返されたデータが正しいかどうかを確認します

    これは、getEmployee()が機能することを証明しています。 3までの手順では、テストプロジェクトでのみコードを使用する必要があります。ステップ4は、アプリケーションコードを呼び出します。私が意図したことは、従業員の作成(ステップ2)は、アプリケーションコードではなくテストプロジェクトコードによって行われるべきであるということです。従業員を作成するアプリケーションコードがある場合(例:CreateEmployee())、これは使用しないでください。同様に、CreateEmployee()をテストすると、GetEmployee()アプリケーションコードは使用しないでください。テーブルからデータを取得するためのテストプロジェクトコードが必要です。

このようにモックはありません! DBを削除して作成する理由は、DBが破損したデータを持つのを防ぐためです。私たちのアプローチでは、テストは何度実行してもパスします。

特別なヒント:ステップ5では、getEmployee()は従業員オブジェクトを返します。後で開発者がフィールド名を削除または変更すると、テストは中断します。開発者が後で新しいフィールドを追加するとどうなりますか?そして、彼/彼女はそれのためのテストを追加することを忘れていますか?テストでは検出されません。解決策は、フィールドカウントチェックを追加することです。例:従業員オブジェクトには4つのフィールド(名、姓、指定、性別)があります。したがって、従業員オブジェクトのフィールドのアサート数は4です。したがって、新しいフィールドが追加されると、カウントのためにテストが失敗し、新しく追加されたフィールドにアサートフィールドを追加するよう開発者に通知します。

そして、これは 統合テストが単体テストよりも優れている の利点を議論する素晴らしい記事です。 (それは言う)

10
Blue Clouds

単体テストのポイントは、一連の条件に基づいてメソッドの動作を分離してテストすることです。モックを使用してテストの条件を設定し、その周りの他のコードと相互作用する方法をチェックすることで、メソッドの動作をアサートします-呼び出す外部メソッドをチェックすることによって、特に条件を指定して返される値をチェックすることによって。

そのため、ActionResultsを返すControllerメソッドの場合、返されたActionResultの値を調べると非常に便利です。

セクション「コントローラーの単体テストの作成」ここでいくつかの非常に明確な例 Moqを使用してください。

コントローラーが連絡先レコードを作成しようとして失敗したときに適切なビューが返されることをテストする、このページの素敵なサンプルを次に示します。

[TestMethod]
public void CreateInvalidContact()
{
    // Arrange
    var contact = new Contact();
    _service.Expect(s => s.CreateContact(contact)).Returns(false);
    var controller = new ContactController(_service.Object);

    // Act
    var result = (ViewResult)controller.Create(contact);

    // Assert
    Assert.AreEqual("Create", result.ViewName);
}
9
unovis

コントローラーを単体テストすることにはあまり意味がありません。通常、それは他の部分を接続するコードの一部にすぎないからです。通常、単体テストには多くのモックが含まれ、他のサービスが正しく接続されていることを確認するだけです。テスト自体は、実装コードを反映しています。

私は統合テストを好みます-具体的なコントローラーからではなく、URLから始め、返されたモデルに正しい値があることを確認します。 Ivonna を使用すると、テストは次のようになります。

var response = new TestSession().Get("/Users/List");
Assert.IsInstanceOf<UserListModel>(response.Model);

var model = (UserListModel) response.Model;
Assert.AreEqual(1, model.Users.Count);

データベースアクセスをモックすることはできますが、別のアプローチをお勧めします。SQLiteのインメモリインスタンスをセットアップし、必要なデータとともに新しいテストごとに再作成します。それはテストを十分に速くしますが、複雑なモックの代わりに、それらを明確にします。 UserService(実装の詳細かもしれません)をモックするのではなく、Userインスタンスを作成して保存するだけです。

8
ulu

通常、単体テストについて話すときは、システム全体ではなく、1つの個別のプロシージャまたはメソッドをテストし、すべての外部依存関係を排除しようとします。

言い換えると、コントローラーをテストするときは、メソッドごとにテストを記述しているので、ビューやモデルをロードする必要さえありません。これらは「モックアウト」する必要のある部分です。その後、モックを変更して、他のテストでは再現が難しい値またはエラーを返すことができます。

1