クラスXがあり、動作X1を検証するいくつかの単体テストを作成します。 Xを依存関係とするクラスAもあります。
Aの単体テストを作成するときは、Xをモックします。つまり、Aを単体テストするときは、Xのモックの動作をX1に設定(仮定)します。時間が経つにつれ、人々はあなたのシステムを使用し、変化が必要になり、Xは進化します。Xを変更して動作X2を表示します。明らかに、Xの単体テストは失敗し、それらを適合させる必要があります。
しかし、Aはどうでしょうか。 Xの動作が変更されても(Xのモックが原因で)、Aのユニットテストは失敗しません。 「実際の」(変更された)Xで実行すると、Aの結果が異なることを検出する方法は?
「単体テストの目的ではない」という答えを期待していますが、単体テストにはどのような価値がありますか?すべてのテストに合格したときに、重大な変更が導入されていないことだけが本当にわかりますか?また、一部のクラスの動作が(喜んでまたは思わず)変化した場合、すべての結果を(できれば自動化された方法で)どのように検出できますか?統合テストにもっと集中すべきではないでしょうか?
Aの単体テストを作成するときは、Xをモックします
あなたは?絶対に必要な場合を除き、私はしません。私はしなければなりません:
X
が遅い、またはX
には副作用がありますこれらのどちらも当てはまらない場合、A
の単体テストでX
もテストされます。それ以外のことを行うと、分離テストが非論理的な極端になります。
コードの他の部分のモックを使用してコードの一部がある場合、私は同意します。そのような単体テストのポイントは何ですか?したがって、これを行わないでください。これらのテストは、実際の依存関係を使用して、その方法ではるかに価値のあるテストを形成できるようにします。
そして、これらのテストを「ユニットテスト」と呼ぶことに不満を感じている人がいたら、単に「自動テスト」と呼んで、優れた自動テストを作成してください。
両方必要です。各ユニットの動作を確認するための単体テストと、正しく接続されていることを確認するためのいくつかの統合テスト。統合テストのみに依存することの問題は、すべてのユニット間の相互作用から生じる組み合わせの爆発です。
クラスAがあり、すべてのパスを完全にカバーするには10の単体テストが必要だとします。次に、別のクラスBがあります。これには、コードが通過できるすべてのパスをカバーするために10回の単体テストが必要です。アプリケーションで、Aの出力をBにフィードする必要があるとします。これで、コードはAの入力からBの出力への100の異なるパスを取ることができます。
単体テストでは、すべてのケースを完全にカバーするために、20の単体テスト+ 1つの統合テストのみが必要です。
統合テストでは、すべてのコードパスをカバーする100のテストが必要になります。
これは、統合テストのみに依存することの欠点についての非常に優れたビデオです J B Rainsberger統合テストは詐欺HD
Aの単体テストを作成するときは、Xをモックします。つまり、Aを単体テストするときは、Xのモックの動作をX1に設定(仮定)します。時間が経つにつれ、人々はあなたのシステムを使用し、変化が必要になり、Xは進化します。Xを変更して動作X2を表示します。明らかに、Xの単体テストは失敗し、それらを適合させる必要があります。
わあ、ちょっと待って。 Xが失敗した場合のテストの意味は、そのように説明するにはあまりにも重要です。
Xの実装をX1からX2に変更すると、Xの単体テストが失敗する場合は、コントラクトXに下位互換性のない変更を加えたことを示しています。
X2は Liskovの意味 のXではないので、他の方法で考える必要がありますあなたの利害関係者(X2によって実装される新しい仕様Yを導入するようなもの).
より深い洞察については、Pieter Hinjens: The End of Software Versions 、またはRich Hickey Simple Made Easy を参照してください。
Aの観点から、コラボレーターが契約Xを尊重するという 前提条件 があります。また、Aの分離テストでは、Aが違反しているコラボレーターを認識しているという保証はないという事実は事実です。 X契約。
レビュー 統合テストは詐欺です ;高レベルでは、X2がコントラクトXを正しく実装していることを確認するために必要な数の分離テストと、AがXからの興味深い応答を与えられて正しいことを実行していることを確認するために必要な数の分離テストが必要です。 X2とAがXの意味に同意することを確認するためのいくつかのintegratedテスト。
solitary テストvs sociable
テストとして表されるこの区別を時々見るでしょう。 Jay Fields 単体テストで効果的に動作する を参照してください。
統合テストにもっと集中すべきではないでしょうか?
繰り返しますが、統合テストは詐欺です。Rainsbergerは、統合(テストスペル)テストに依存しているプロジェクトに(彼の経験では)よくある肯定的なフィードバックループについて詳しく説明しています。要約すると、分離/孤立テストが設計に圧力をかけることなく、品質が低下し、より多くのミスとより統合されたテストにつながります...
(一部の)統合テストも必要になります。複数のモジュールによって導入される複雑さに加えて、これらのテストを実行すると、独立したテストよりも抵抗が大きくなる傾向があります。作業が進行しているときに非常に高速なチェックを繰り返し、「完了」したと思われるときに追加のチェックを保存する方が効率的です。
まず、質問の核となる前提に欠陥があると言いましょう。
実装をテスト(またはモック)することはありません。テスト(およびモック)interfacesです。
インターフェイスX1を実装する実際のクラスXがある場合、X1にも準拠するモックXMを作成できます。次に、クラスAはX1を実装するものを使用する必要があります。これは、クラスXまたはモックXMのいずれかです。
ここで、Xを変更して新しいインターフェースX2を実装するとします。まあ、明らかに私のコードはコンパイルできなくなりました。 AはX1を実装するものを必要とし、それはもう存在しません。この問題は確認されており、修正できます。
X1を置き換える代わりに、X1を変更するとします。これでクラスAが設定されました。ただし、モックXMはインターフェースX1を実装しなくなりました。この問題は確認されており、修正できます。
単体テストとモックの基本は、インターフェースを使用するコードを記述することです。インターフェイスのコンシューマーは気にしませんhowコードが実装されますが、同じコントラクトが(入力/出力)に準拠しているだけです。
これは、メソッドに副作用がある場合に機能しなくなりますが、「単体テストやモックはできない」として安全に除外できると思います。
順番に質問をする:
単体テストにはどのような価値がありますか
作成して実行するのが安く、早い段階でフィードバックを得ることができます。 Xを壊した場合、良いテストがあるかどうかはすぐにわかります。すべての層を(単体でもデータベースでも)単体テストしていない限り、統合テストの作成も検討しないでください。
すべてのテストに合格したときに、重大な変更が導入されていないことを本当に伝えているだけですか?
合格したテストがあると、実際にはほとんど何もわかりません。十分なテストを記述していない可能性があります。十分なシナリオをテストしていない可能性があります。コードカバレッジはここで役立ちますが、特効薬ではありません。 alwaysに合格するテストがあるかもしれません。したがって、赤はしばしば見落とされがちな赤、緑、リファクタリングの最初のステップです。
また、一部のクラスの動作が(喜んでまたは思わず)変化すると、すべての結果を(できれば自動化された方法で)どのように検出できるでしょうか。
より多くのテスト-ツールはどんどん良くなっていますが。ただし、インターフェイスでクラスの動作を定義する必要があります(以下を参照)。 N.B.テストピラミッドの上には常に手動テストの場所があります。
統合テストにもっと集中すべきではないでしょうか?
さらに多くの統合テストも答えではありません。それらを作成、実行、維持するには費用がかかります。ビルドの設定によっては、ビルドマネージャーがそれらを除外して、開発者が覚えていることに依存するようにすることがあります(決して良いことではありません!)。
開発者が優れた単体テストがあれば、5分で見つけた壊れた統合テストを修正するために何時間も費やすのを見てきました。これに失敗した場合は、ソフトウェアを実行してみてください。エンドユーザーが気にするのはこれだけです。ユーザーがスイート全体を実行したときにカードの家全体が落ちた場合、何百万ものユニットテストに合格しても意味がありません。
クラスAが同じ方法でクラスXを消費することを確認する場合は、コンクリーションではなくインターフェイスを使用する必要があります。次に、互換性のない変更がコンパイル時に取得される可能性が高くなります。
そのとおりです。
ユニットテストは、ユニットの分離された機能をテストするためのものであり、意図したとおりに機能し、愚かなエラーが含まれていないことを一目で確認できます。
ユニットテストは、アプリケーション全体が機能することをテストするためのものではありません。
多くの人が忘れているのは、ユニットテストはコードを検証するための最も迅速で汚れた手段にすぎないということです。小さなルーチンが機能することがわかったら、統合テストも実行する必要があります。単体テストは、単体テストよりもわずかに優れています。
私たちがユニットテストをまったく持っている理由は、それらが安価であることを想定しているからです。作成、実行、保守が簡単です。それらを最小統合テストに変え始めると、あなたは苦痛の世界に入ります。完全な統合テストを行って、単体テストを完全に無視することもできます。
現在、一部の人々は、ユニットはクラス内の単なる関数ではなく、クラス全体(自分自身を含む)であると考えています。ただし、これはユニットのサイズを大きくするだけなので、統合テストの必要性は少なくなりますが、それでも必要です。完全な統合テストスイートなしでは、プログラムが想定どおりの動作をすることを確認することは依然として不可能です。
さらに、ライブ(またはセミライブ)システムで完全な統合テストを実行して、顧客が使用する条件で動作することを確認する必要があります。
ユニットテストは何の正しさも証明しません。これはすべてのテストに当てはまります。通常、ユニットテストは、契約ベースの設計(契約による設計は別の言い方をすると言います)と組み合わされ、正確性を定期的に検証する必要がある場合は、自動化された正確性の証明が可能です。
クラスの不変条件、前提条件、およびポスト条件で構成される実際のコントラクトがある場合、より高いレベルのコンポーネントの正確性を、より低いレベルのコンポーネントのコントラクトに基づいて、階層的に正確であることを証明できます。これは、契約による設計の背後にある基本的な概念です。
模擬テストがあまり役立たないことがわかりました。ほとんどの場合、私は元のクラスがすでに持っている振る舞いを再実装することになり、モックの目的を完全に無効にします。
M.e.より適切な戦略は、懸念事項を適切に分離することです(たとえば、パートBからZまでを持ち込むことなく、アプリのパートAをテストできます)。このような優れたアーキテクチャは、優れたテストを書くのに本当に役立ちます。
また、副作用をロールバックできる限り、私は副作用を受け入れる用意があります。私のメソッドがデータベースのデータを変更する場合は、それを許可してください! dbを前の状態にロールバックできる限り、何が問題になりますか?また、私のテストでデータが期待どおりに見えるかどうかを確認できるという利点もあります。インメモリDBまたは特定のテストバージョンのdbが実際に役立ちます(例:RavenDBのインメモリテストバージョン)。
最後に、サービスの境界をモックするのが好きです。サービスbに対してそのhttp呼び出しを行わないでください。しかし、それを傍受して適切なものを導入しましょう
両方の陣営の人々が、クラステストと行動テストは直交していないことを理解してほしい。
クラステストとユニットテストは同じ意味で使用されますが、使用すべきではありません。一部の単体テストは、たまたまクラスに実装されています。以上です。ユニットテストは、クラスのない言語で何十年もの間行われてきました。
テストの振る舞いに関しては、GWTコンストラクトを使用してクラステスト内でこれを行うことは完全に可能です。
さらに、自動化されたテストがクラスに沿って進むのか、行動に沿って進むのかは、むしろ優先順位に依存します。一部は迅速にプロトタイプを作成し、ドアから何かを入手する必要がありますが、他のものは自社スタイルによるカバレッジ制約があります。多くの理由。どちらも完全に有効なアプローチです。あなたはあなたのお金を支払い、あなたはあなたの選択をします。
だから、コードが壊れたときに何をすべきか。それがインターフェースにコード化されている場合は、結露だけを変更する必要があります(テストとともに)。
ただし、新しい動作を導入しても、システムが危険にさらされる必要はありません。 Linuxなどは非推奨の機能でいっぱいです。また、コンストラクター(およびメソッド)などは、すべての呼び出しコードを強制的に変更せずに、オーバーロードすることができます。
クラステストが有効なのは、まだ(まだ時間の制約、複雑さなどのために)組み込まれていないクラスに変更を加える必要がある場合です。包括的なテストがあれば、クラスを始めるのがはるかに簡単になります。
Xのインターフェースが変更されない限り、Aに関連するものは何も変更されていないため、Aの単体テストを変更する必要はありません。あなたは本当にXとAの単体テストを書いたように聞こえますが、それをAの単体テストと呼んでいました。
Aの単体テストを作成するときは、Xをモックします。つまり、Aを単体テストするときは、Xのモックの動作をX1に設定(仮定)します。
理想的には、Xのモックはすべての可能性がある Xの動作だけでなく、Xの動作expectをシミュレートする必要があります。したがって、実際にXに実装するものに関係なく、Aはすでにそれを処理することができる。したがって、インターフェース自体を変更する以外に、Xを変更しても、Aの単体テストに影響はありません。
例:Aがソートアルゴリズムで、Aがソートするデータを提供するとします。 Xのモックは、nullの戻り値、空のデータリスト、単一の要素、既に並べ替えられた複数の要素、まだ並べ替えられていない複数の要素、逆に並べ替えられた複数の要素、同じ要素が繰り返されるリスト、null値が混在している、とんでもない要素が多数あり、例外もスローする必要があります。
したがって、おそらくXは最初は月曜日にソートされたデータを返し、火曜日には空のリストを返しました。しかし、Xが月曜日にソートされていないデータを返し、火曜日に例外をスローするとき、Aは気にしません-これらのシナリオはすでにAの単体テストでカバーされています。