リポジトリメソッドをテストするためにユニットテストが必要なのはなぜですか?


19

私は経験不足のためにそれをうまく防御できないので、この質問について少し主張する悪魔を演じる必要があります。これが契約です。概念的には、単体テストと統合テストの違いを理解できます。永続化メソッドとリポジトリに特に焦点を当てる場合、単体テストでは、おそらくMoqなどのフレームワークを介してモックを使用し、検索された順序が期待どおりに返されたと主張します。

次の単体テストを作成したとしましょう。

[TestMethod]
public void GetOrderByIDTest()
{
   //Uses Moq for dependency for getting order to make sure 
   //ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}

したがって、セットアップしOrderIdExpected = 5て、モックオブジェクトが5IDとして返されると、テストに合格します。わかった。コードを単体テストして、コードのプリフォームが期待するオブジェクトとIDを返し、他の何かは返さないことを確認しました。

私が得る議論はこれです:

「なぜ単体テストをスキップして統合テストを行わないのですか?データベースのストアドプロシージャコードを一緒にテストすることが重要です。テストに時間がかかることはわかっていますが、テストを実行してテストする必要があるので、両方を持っていても意味がありません。重要なことをテストするだけです。」

「まあ、それは統合テストであり、ユニットテストとしてコードを個別にテストする必要があります。やだ、やだ、やだ...」などの教科書の定義でそれを守ることができます。対現実は失われつつあります。私は時々これに出くわし、最終的に外部依存関係に依存する単体テストコードの背後にある理由を擁護できない場合、それを主張することはできません。

この質問に関するヘルプは大歓迎です、ありがとう!


2
私は...彼らはとにかく要件を変更しようとしているまっすぐなユーザテストにそれを送ると言う...
ネイサンはヘイフィールド

時々物事を軽く保つために皮肉のために+1
atconway

2
簡単な答えは、各メソッドにx個のエッジケースがある場合があるということです(正のID、0のID、および負のIDをテストするとしましょう)。エッジケースが3つあるこのメソッドを使用する機能をテストする場合は、エッジケースの各組み合わせをテストするために9つのテストケースを記述する必要があります。それらを分離することで、6を書くだけで済みます。さらに、テストは、何かが壊れた理由のより具体的なアイデアを提供します。おそらく、リポジトリは失敗するとnullを返し、null例外がコードの数百行下にスローされます。
ロブ

「ユニット」テストの定義が厳しすぎると思います。リポジトリクラスの「作業単位」とは何ですか?
カレブ

そして、あなたがこれを検討していることを確認してください:あなたのユニットテストがあなたがテストしようとしているすべてをモックするなら、あなたは本当に何をテストしていますか?
カレブ

回答:


20

単体テストと統合テストの目的は異なります。

単体テストでは、コード機能を検証します ...メソッドを呼び出すと、メソッドから期待どおりの結果が得られることを確認します。統合テストは、システムとして組み合わせた場合のコードの動作をテストします 単体テストでシステムの動作を評価することも、統合テストで特定のメソッドの特定の出力を検証することも期待していません。

単体テストは、正しく実行されると、統合テストよりも簡単にセットアップできます。統合テストのみに依存している場合、テストは次のようになります。

  1. 全体的に書くのが難しくなります
  2. 必要な依存関係がすべてあるため、より脆くなる
  3. 少ないコードカバレッジを提供します。

結論:統合テストを使用してオブジェクト間の接続が適切に機能していることを確認しますが、最初にユニットテストに頼って機能要件を確認します。


とはいえ、特定のリポジトリメソッドではユニットテストは必要ない場合があります。単純な方法で単体テストを行うべきではありません。オブジェクトのリクエストをORM生成コードに渡し、結果を返すだけであれば、ほとんどの場合、ユニットテストする必要はありません。統合テストで十分です。


はい、迅速な対応に感謝します。応答に対する私の唯一の批判は、それが私の最後の段落の懸念事項に該当することです。私の反対派はまだ抽象的な説明や定義を聞いているだけであり、より深い推論はありません。たとえば、コードに関連してストアドプロシージャ/ DBをテストするユースケースに適用される推論と、この特定のユースケースに関して単体テストがまだ価値がある理由を教えてください。
atconway

1
SPがデータベースから結果を返すだけの場合、統合テストで十分な場合があります。自明でないロジックが含まれている場合、単体テストが示されます。stackoverflow.com/questions/1126614/…およびmsdn.microsoft.com/en-us/library/aa833169(v=vs.100).aspx(SQL Server固有)も参照してください
ロバートハーベイ

12

私はプラグマティストの側にいます。コードを2回テストしないでください。

リポジトリの統合テストのみを作成します。これらのテストは、インメモリデータベースに対して実行される単純なテストセットアップにのみ依存しています。単体テストが行​​うすべての機能などを提供すると思います。

  1. TDDを行う場合、ユニットテストの代わりに使用できます。実際のコードを開始する前に記述するテストコードのボイラープレートがいくつかありますが、すべてが整ったら、レッド/グリーン/リファクタリングアプローチで非常にうまく機能します。
  2. リポジトリの実際のコード(SQL文字列またはORMコマンドに含まれるコード)をテストします。実際に何らかの文字列をStatementExecutorに送信したことを確認するよりも、クエリが正しいことを確認する方が重要です。
  3. 回帰テストとして優れています。失敗した場合、それは常に、考慮されていないスキーマの変更などの実際の問題が原因です。
  4. データベーススキーマを変更する場合、これらは不可欠です。テストに合格する限り、アプリケーションを中断するような方法でスキーマを変更していないと確信できます。(この場合、単体テストは役に立たない。なぜならストアドプロシージャが存在しなくなっても、単体テストは合格するからだ。)

実際、非常に実用的です。ORMがわずかに異なるため、インメモリデータベースが実際のデータベースと(パフォーマンスの点ではなく)実際に異なる動作をする場合があります。統合テストではその点に注意してください。
マルセル

1
@Marcel、私はそれに遭遇しました。実際のデータベースに対してすべてのテストを時々実行することで解決しました。
ウィンストンイーバート

4

単体テストは、統合テスト(設計上)では不可能なレベルのモジュール性を提供します。システムがリファクタリングまたは再構成された場合(およびこれ発生します)、ユニットテストはしばしば再利用できますが、統合テストは頻繁に書き直す必要があります。ユニットテストにあるべきものの作業を行おうとする統合テストは、多くの場合、あまりにも多くのことを行うため、保守が難しくなります。

さらに、単体テストを含めることには、次の利点があります。

  1. 単体テストを使用すると、失敗した統合テスト(おそらく回帰バグによる)をすばやく分解し、原因を特定できます。さらに、これにより、図や他の文書よりも迅速にチーム全体に問題が伝えられます。
  2. 単体テストは、コードとともに例およびドキュメント(実際にコンパイルされるドキュメントの一種)として機能します。他のドキュメントとは異なり、古くなったことがすぐにわかります。
  3. 単体テストは、より大きなパフォーマンスの問題を分解しようとするときのベースラインパフォーマンスインジケーターとして機能しますが、統合テストでは、問題を見つけるために多くの計測が必要になる傾向があります。

単体テストと統合テストの両方を使用しながら、作業を分割(およびDRYアプローチを実施)することができます。機能の小さなユニットについてはユニットテストに依存するだけで、統合テストのユニットテストに既にあるロジックを繰り返さないでください。これにより、多くの場合、作業量が少なくなります(したがって、再作業が少なくなります)。


4

テストは、破損した場合に役立ちます:プロアクティブまたはリアクティブ。

ユニットテストは事前対応型であり、統合テストが段階的/偽のデータに対して事後対応型であるのに対し、継続的な機能検証である場合があります。

検討中のシステムが計算ロジックよりもデータに近い/データに依存している場合、統合テストの重要性が高まります。

たとえば、ETLシステムを構築している場合、最も関連性のあるテストはデータ(ステージング、偽造、またはライブ)の周りで行われます。同じシステムには、いくつかの単体テストがありますが、検証、フィルタリングなどに限られます。

リポジトリパターンに計算ロジックまたはビジネスロジックを含めることはできません。データベースに非常に近いです。しかし、リポジトリはそれを使用するビジネスロジックにも近いものです。だから、それはバランスを打つことの問題です。

単体テストでは、リポジトリの動作をテストできます。統合テストでは、リポジトリが呼び出されたときに本当に起こったことをテストできます。

今、「本当に起こったこと」は非常に魅力的なように見えるかもしれません。しかし、これは、一貫して繰り返し実行するには非常に高価になる可能性があります。ここでは、脆性試験の古典的な定義が適用されます。

そのため、ほとんどの場合、リポジトリを使用するオブジェクトで単体テストを作成するだけで十分だと思います。このようにして、適切なリポジトリメソッドが呼び出され、適切なモック結果が返されるかどうかをテストします。


私はこれが好きです:「今、「本当に起こったこと」は非常に魅力的であるように思えるかもしれません。しかし、これは一貫して繰り返し実行するために非常に高価になるかもしれません。」
atconway

2

テストが必要なものは3つあります。ストアドプロシージャ、ストアドプロシージャを呼び出すコード(つまり、リポジトリクラス)、およびコンシューマです。リポジトリの仕事は、クエリを生成し、返されたデータセットをドメインオブジェクトに変換することです。クエリの実際の実行やデータセットの作成とは別の単体テストをサポートするのに十分なコードがあります。

(これは非常に単純化された例です):

interface IOrderRepository
{
    Order GetOrderByID(Guid id);
}

class OrderRepository : IOrderRepository
{
    private readonly ISqlExecutor sqlExecutor;
    public OrderRepository(ISqlExecutor sqlExecutor)
    {
        this.sqlExecutor = sqlExecutor;
    }

    public Order GetOrderByID(Guid id)
    {
        var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
        var dataset = this.sqlExecutor.Execute(sql, p0 = id);
        var result = this.orderFromDataset(dataset);
        return result;
    }
}

次に、テスト時OrderRepositoryに、モックさISqlExecutorれたものを渡し、テスト中のオブジェクトが正しいSQL(そのジョブ)を通過し、Order何らかの結果データセット(モックされた)が与えられた適切なオブジェクトを返すことを確認します。おそらく、具象SqlExecutorクラスをテストする唯一の方法は統合テストを使用することです。これは十分に公平ですが、それは薄いラッパークラスであり、めったに変更されることはありません。

ストアドプロシージャもユニットテストする必要があります。


0

これを非常に単純な考え方にまとめることができます。バグを見つけるのに役立つため、ユニットテストを優先します。一貫性がないと混乱を招くため、これを一貫して行います。

さらなる理由は、テストにも適用されるため、オブジェクト指向の開発をガイドする同じルールに由来します。たとえば、単一の責任原則。

いくつかの単体テストが実行する価値がないと思われる場合は、おそらく、被験者が実際に持つ価値がないという兆候です。または、その機能は対応する機能よりも抽象的であり、そのテスト(およびコード)はより高いレベルに抽象化できます。

何でもそうですが、例外もあります。また、プログラミングはいまだに芸術的なものであるため、多くの問題は十分に異なるため、それぞれを最適なアプローチで評価する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.