web-dev-qa-db-ja.com

統合テストはすべての単体テストを繰り返すことを意図していますか?

私が関数を持っているとしましょう(Rubyで書かれていますが、誰でも理解できるはずです):

def am_I_old_enough?(name = 'filip')
   person = Person::API.new(name)
   if person.male?
      return person.age > 21
   else
      return person.age > 18
   end
end

単体テストでは、すべてのシナリオをカバーする4つのテストを作成します。それぞれがモックされたPerson::APIオブジェクトとスタブ化されたメソッドmale?およびageを使用します。

次に、統合テストを作成します。 Person :: APIはこれ以上モック化されるべきではないと思います。したがって、私はまったく同じ4つのテストケースを作成しますが、Person :: APIオブジェクトをモックすることはありません。あれは正しいですか?

はいの場合、単体テストを書くことのポイントは何ですか?私に自信を与える統合テストを書くことができれば(スタブやモックではなく実際のオブジェクトで作業するので)?

37
Filip Bartuzi

いいえ、統合テストは単体テストのカバレッジを複製するだけではありません。それらはかもしれないいくつかの報道を複製しますが、それはポイントではありません。

単体テストのポイントは、特定の小さな機能が意図したとおりに完全に機能することを確認することです。 am_i_old_enoughの単体テストでは、異なる年齢のデータをテストします。確かにしきい値に近いもので、おそらくall発生する人間の年齢です。このテストを作成した後は、am_i_old_enoughの整合性が問題になることはありません。

統合テストのポイントは、システム全体、または相当数のコンポーネントの組み合わせが、一緒に使用されたときに正しいことを確認することです。顧客はあなたが書いた特定のユーティリティ関数については気にせず、彼らのWebアプリが未成年者によるアクセスから適切に保護されていることを気にします。

ユーザーの年齢のチェックはoneその機能のごく一部ですが、統合テストはユーティリティ関数が正しいしきい値を使用しているかどうかをチェックしません。呼び出し元がそのしきい値に基づいて正しい決定を行うかどうか、ユーティリティ関数がまったく呼び出されるかどうか、アクセスのother条件を満たすかどうかなどをテストします。 。

両方のタイプのテストが必要な理由は、基本的に、コードベースを介して実行される可能性のあるシナリオの組み合わせが爆発的に増えるためです。ユーティリティ関数に可能な入力が約100個あり、ユーティリティ関数が数百ある場合、allの場合に正しいことが行われることを確認するには、多くのものが必要になります。何百万ものテストケース。非常に小さなスコープですべてのケースをチェックし、次にこれらのスコープの一般的、関連または可能性のある組み合わせをチェックすることにより、ユニットテストで示されているように、これらの小さなスコープが既に正しいと仮定すると、システムが本来あるべきことを実行しているというかなり信頼できる評価を得ることができますテストする別のシナリオで溺死。

72
Kilian Foth

短い答えは「いいえ」です。より興味深い部分は、この状況が発生する理由/方法です。

厳密なプラクティスに準拠していないように見えるコードに対して、厳密なテストプラクティス(単体テストvs統合テスト、モックなど)に準拠しようとしているため、混乱が生じていると思います。

これは、コードが「間違っている」、または特定のプラクティスが他のプラクティスより優れていると言っているのではありません。単に、テストの実践によって行われた仮定の一部がこの状況では適用されない可能性があること、そしてコーディングの実践とテストの実践で同様のレベルの「厳格さ」を使用することが役立つ場合があるということです。または、少なくとも、それらが不均衡である可能性があることを認めるために、一部の側面が適用不可または冗長になる可能性があります。

最も明白な理由は、関数が2つの異なるタスクを実行していることです。

  • 名前に基づいてPersonを検索します。これには、おそらく他の場所で作成/保存されていると思われるPersonオブジェクトを確実に見つけられるように、統合テストが必要です。
  • 性別に基づいて、Personが十分古いかどうかを計算します。これには、計算が期待どおりに実行されることを確認するための単体テストが必要です。

これらのタスクを1つのコードブロックにグループ化することで、他のコードなしでは実行できません。計算を単体テストする場合、Personを(実際のデータベースまたはスタブ/モックから)検索する必要があります。ルックアップがシステムの残りの部分と統合されていることをテストする場合は、経過時間の計算も実行する必要があります。その計算で何をすべきか?それを無視するべきか、それともチェックするべきか?それはあなたがあなたの質問で説明している正確な苦境のようです。

代替案を想像すると、それだけで計算ができます。

def is_old_enough?(person)
   if person.male?
      return person.age > 21
   else 
      return person.age > 18
   end
end

これは純粋な計算であるため、統合テストを実行する必要はありません。

ルックアップタスクも個別に作成したくなるかもしれません。

def person_from_name(name = 'filip')
   return Person::API.new(name)
end

ただし、この場合、機能はPerson::API.newに非常に近いため、代わりにそれを使用する必要があります(デフォルト名が必要な場合は、クラス属性など、他の場所に保存する方がよいでしょうか?)。

Person::API.new(またはperson_from_name)の統合テストを作成する際に注意する必要があるのは、期待どおりのPersonが返されるかどうかだけです。年齢ベースの計算はすべて別の場所で行われるため、統合テストでは無視できます。

14
Warbo

私が追加したいもう1つのポイント Killianの答え は、単体テストが非常に速く実行されるため、数千のテストを実行できることです。統合テストは、Webサービス、データベース、またはその他の外部依存関係を呼び出すため、通常は時間がかかります。統合テストでは時間がかかりすぎるため、同じテスト(1000)を実行することはできません。

また、ユニットテストは通常​​build時に(ビルドマシン上で)実行され、統合テストはdeployment環境/マシン。

通常、ビルドごとに1000件のユニットテストを実行し、その後、展開ごとに100件ほどの高価値の統合テストを実行します。ビルドごとに展開することはできませんが、統合テストを実行するために実行するビルドのため、問題ありません。通常は、展開を長引かせたくないため、これらのテストを10分または15分以内に実行するように制限します。

さらに、毎週のスケジュールで、週末やその他のダウンタイムにより多くのシナリオをカバーする一連の回帰テストを実行する場合があります。より多くのシナリオがカバーされるため、これらは15分より長くかかる場合がありますが、通常、土日は誰も作業していないため、テストにもっと時間をかけることができます。

11
Jon Raynor