一部の人々 統合テストはあらゆる種類の悪いことと間違っていることを維持します -すべてはユニットテストする必要があります。つまり、依存関係をモックする必要があります。さまざまな理由から、私はいつも好きではないオプションです。
場合によっては、単体テストでは何も証明されないことがあります。
例として、次の(簡単で素朴な)リポジトリ実装(PHPで)を取り上げます。
_class ProductRepository
{
private $db;
public function __construct(ConnectionInterface $db) {
$this->db = $db;
}
public function findByKeyword($keyword) {
// this might have a query builder, keyword processing, etc. - this is
// a totally naive example just to illustrate the DB dependency, mkay?
return $this->db->fetch("SELECT * FROM products p"
. " WHERE p.name LIKE :keyword", ['keyword' => $keyword]);
}
}
_
このリポジトリが実際にさまざまなキーワードに一致する製品を見つけられることをテストで証明したいとします。
実際の接続オブジェクトとの統合テストが不足している場合、これが実際に実際のクエリを生成していること、およびそれらのクエリが実際に私が思っていることを実行していることをどのように知ることができますか?
単体テストで接続オブジェクトをモックする必要がある場合、「期待されるクエリを生成する」などのことしか証明できませんが、それは実際に動作することを意味するわけではありません...つまり、私が期待したクエリを生成しているのかもしれませんが、そのクエリは私が思っていることを実行していないのかもしれません。
つまり、生成されたクエリについてアサーションを作成するテストのように感じます。これは、findByKeyword()
メソッドがimplemented、しかしそれはそれが実際に機能することを証明していません。
この問題は、リポジトリやデータベース統合に限定されません-モック(test-double)の使用についてアサーションを作成すると、実装の方法が証明されるだけで、実装されるかどうかは証明されない場合が多いようです。実際に動作します。
このような状況にどう対処しますか?
このような場合、統合テストは本当に「悪い」のでしょうか?
私は1つのことをテストする方が良いという点を理解しました。また、統合テストが無数のコードパスにつながる理由も理解しています。これらすべてをテストすることはできませんが、唯一の目的がサービス(リポジトリなど)の場合です。別のコンポーネントとやり取りするには、統合テストなしで実際に何かをテストするにはどうすればよいですか?
あなたの同僚は、ユニットテストできるすべてのものがユニットテストされるべきであるということは正しく、特に複雑な外部サービスの単純なラッパーを記述する場合、ユニットテストはこれまでしか実行できず、それ以上は実行できないというのは正しいことです。
テストについての一般的な考え方は、testing pyramidとしてです。これはアジャイルと頻繁に関連する概念であり、多くの人が Martin Fowler (Succeeding with Agileを含む)を含めてそれについて書いています)、 Alistair Scott 、および Google Testing Blog 。
/\ --------------
/ \ UI / End-to-End \ /
/----\ \--------/
/ \ Integration/System \ /
/--------\ \----/
/ \ Unit \ /
-------------- \/
Pyramid (good) Ice cream cone (bad)
概念は、高速で回復力のある単体テストがテストプロセスの基盤であるということです。システム/統合テストよりも集中的な単体テスト、エンドツーエンドテストよりも多くのシステム/統合テストが必要です。上位に近づくにつれて、テストは実行に多くの時間/リソースを必要とする傾向があり、脆性と不安定性の影響を受ける傾向があり、どのシステムまたはファイルかを特定するのにあまり具体的ではありません壊れた;当然のことながら、「トップヘビー」にならないようにすることをお勧めします。
これまでのところ、統合テストはbadではありませんが、それらに大きく依存している場合、個々のコンポーネントをテストしやすいように設計していない可能性があります。ここでの目標はであり、他の壊れやすいシステムを最小限に抑えながら、ユニットが仕様どおりに動作していることをテストすることです:インメモリデータベース(たとえば、ヘビーエッジケーステストの場合は、単体テストに適したテスト(モックと一緒に2倍)としてカウントし、実際のデータベースエンジンを使用していくつかの統合テストを記述し、システムが組み立てられたときにメインケースが機能することを確認します。 。
付記として、あなたが書くモックは単に何かがどのように実装されているかをテストし、それが機能するかどうかではなく、をテストすることを述べました。これはアンチパターンのようなものです。その実装の完全なミラーであるテストは、実際には何もテストしていません。代わりに、必要な抽象化またはリアリズムのレベルで、すべてのクラスまたはメソッドが独自の仕様に従って動作することをテストしてください。
私の同僚の1人は、統合テストはあらゆる種類の悪い点であり、すべてを単体テストする必要があると主張しています。
それは抗生物質が悪いと言っているようなものです-すべてはビタミンで治されるべきです。
ユニットテストできないすべてをキャッチ-コンポーネントの動作をテストするだけ制御された環境で。統合テストは、すべてが機能することを確認しますtogether。これは、実行が困難ですが、最終的にはより意味があります。
優れた包括的なテストプロセスでは、両方テストの種類-単体テストを使用して、独立してテストできるビジネスルールなどを検証し、統合テストですべてが連携して動作することを確認します。
実際の接続オブジェクトとの統合テストが不足している場合、これが実際に実際のクエリを生成していること、およびそれらのクエリが実際に私が行っていると思うことを実行していることをどうやって知ることができますか?
あなたはcould単体テストを行いますデータベースレベルで。さまざまなパラメーターを使用してクエリを実行し、期待どおりの結果が得られるかどうかを確認します。許可されたとは、変更をコピーして「真の」コードに貼り付けることを意味します。しかしdoesを使用すると、他の依存関係に関係なくクエリをテストできます。
単体テストはすべての欠陥をキャッチするわけではありません。しかし、他の種類のテストと比較して、セットアップと(再)実行の方が安価です。ユニットテストは、中程度の値と低から中程度のコストの組み合わせによって正当化されます。
次の表は、さまざまな種類のテストの欠陥検出率を示しています。
出典: p.470 in Code Complete 2 by McConnell
いいえ、悪くありません。うまくいけば、単体テストと統合テストが必要です。これらは、開発サイクルのさまざまな段階で使用および実行されます。
単体テスト
ユニットテストは、コードがコンパイルされた後、ビルドサーバーとローカルで実行する必要があります。単体テストが失敗した場合は、ビルドが失敗するか、テストが修正されるまでコードの更新をコミットしないでください。単体テストを分離する理由は、ビルドサーバーがすべてのテストをすべての依存関係なしで実行できるようにするためです。次に、複雑な依存関係をすべて必要とせずにビルドを実行し、非常に高速に実行される多くのテストを実行できます。
したがって、データベースの場合、次のようなものが必要です。
IRespository
List<Product> GetProducts<String Size, String Color);
これで、IRepositoryの実際の実装はデータベースに移動して製品を取得しますが、ユニットテストの場合、Irepositoryを偽物でモックして、製品のあらゆる種類のリストをシミュレートできるため、actaulデータベースなしで必要に応じてすべてのテストを実行できますモックインスタンスから返され、モックされたデータでビジネスロジックをテストします。
統合テスト
統合テストは通常、境界交差テストです。これらのテストを展開サーバー(実際の環境)、サンドボックス、またはローカル(サンドボックスを指す)で実行したいと考えています。ビルドサーバーでは実行されません。ソフトウェアが環境に展開された後、通常これらは展開後のアクティビティとして実行されます。コマンドラインユーティリティを使用して自動化できます。たとえば、呼び出す統合テストをすべて分類すると、コマンドラインからnUnitを実行できます。これらは実際に、実際のデータベース呼び出しで実際のリポジトリを呼び出します。これらのタイプのテストは、次のことに役立ちます。
これらのテストは、セットアップや破棄が必要になる場合があるため、実行が困難な場合があります。製品の追加を検討してください。おそらく製品を追加し、追加されたかどうかを確認するために照会し、完了したら削除します。数百または数千の「統合」製品を追加したくないので、追加のセットアップが必要です。
統合テストは、環境を検証し、本物が機能することを確認するために非常に価値があることが証明できます。
両方が必要です。
データベース統合テストは悪くありません。さらに、それらは必要です。
あなたはおそらくあなたのアプリケーションをレイヤーに分割していて、それは良いことです。隣接するレイヤーをモックすることで、各レイヤーを個別にテストできます。これも良いことです。しかし、作成する抽象化レイヤーの数に関係なく、ある時点で、ダーティな作業を行うレイヤーが必要になります。実際にはデータベースと通信します。テストしない限り、テストはしないでくださいです。レイヤーnをモックしてレイヤーn-1をテストする場合、レイヤーnが機能するという前提を評価します条件でレイヤーn-1は機能します。これが機能するためには、レイヤー0が機能することを何らかの方法で証明する必要があります。
理論的には、テストデータベースを単体テストすることもできますが、生成されたSQLを解析して解釈することで、オンザフライでテストデータベースを作成して対話する方がはるかに簡単で信頼性が高くなります。
結論
Abstract Repository、Ethereal Object-Relational-Mapper、Generic Active Record、Theoretic Persistenceレイヤーのユニットテストから得られる信頼度は、最後に、生成されたSQLに構文エラーが含まれている場合は?
あなたが参照する ブログ記事 の著者は、主に統合テストから生じる可能性のある潜在的な複雑性に関係しています(ただし、非常に独断的でカテゴリー的な方法で書かれています)。ただし、統合テストは必ずしも悪いわけではなく、純粋な単体テストより実際に役立つテストもあります。それは実際にはアプリケーションのコンテキストとテストしようとしているものに依存します。
今日の多くのアプリケーションは、データベースサーバーがダウンすると、まったく機能しなくなります。少なくとも、テストしようとしている機能のコンテキストで考えてください。
一方では、テストしようとしているものがデータベースに依存しない、またはまったく依存しないようにできる場合は、テストを使用しないようにテストを記述します。データベース(必要に応じてモックデータを提供するだけ)。たとえば、Webページを提供するときに認証ロジックをテストしようとしている場合(たとえば)、それをDBから完全に切り離すことはおそらく良いことです(認証にDBに依存していない場合、またはあなたはそれを合理的に簡単にあざけることができます)。
一方、それがデータベースに直接依存する機能であり、データベースが使用できない場合、実際の環境ではまったく機能しない場合は、DBクライアントコード(つまり、 DB)は必ずしも意味がありません。
たとえば、アプリケーションがデータベース(場合によっては特定のデータベースシステム)に依存することがわかっている場合、そのためにデータベースの動作をモックすることは、多くの場合時間の無駄になります。データベースエンジン(特にRDBMS)は複雑なシステムです。数行のSQLは実際には多くの作業を実行できますが、シミュレーションが困難です(実際、SQLクエリが数行の長さの場合、Java/PHP/C#/ Pythonの行がさらに必要になる可能性があります。内部で同じ結果を生成するコード):DBにすでに実装しているロジックを複製することは意味がなく、テストコードをチェックすること自体が問題になります。
私は必ずしもこれを単体テストの問題として扱うわけではありません。 統合テストですが、テストされているもののスコープを見てください。単体テストと統合テストの全体的な問題は残ります。テストデータとテストケースの合理的に現実的なセットが必要ですが、テストをすばやく実行するには十分に小さいものも必要です。
データベースをリセットしてテストデータを再入力する時間は、考慮すべき側面です。通常は、そのモックコードを書くのにかかる時間(最終的には維持する必要があります)に対してこれを評価します。
考慮すべきもう1つのポイントは、アプリケーションとデータベースとの依存関係の程度です。
両方必要です。
あなたの例では、特定の条件でデータベースをテストしていた場合、findByKeyword
メソッドを実行するとデータが返され、これは細かい統合テストであると期待できます。
そのfindByKeyword
メソッドを使用している他のコードでは、テストへのフィードを制御したいので、nullやテストに適した単語、またはデータベースの依存関係を模倣したものを返すことができます。テストが受け取る内容を正確に把握します(データベースに接続し、その中のデータが正しいことを確認するオーバーヘッドを失う)
そのような単体テストを不完全だと考えるのは正しいことです。不完全性は、モックされているデータベースインターフェースにあります。そのような素朴なモックの期待や主張は不完全です。
それを完了するには、SQLステートメントが発行される保証ができるSQLルールエンジンを作成または統合するのに十分な時間とリソースを割く必要がありますテスト中のサブジェクトは、期待どおりの動作になります。
ただし、モックの代わりによく忘れられていくらか高価な代替/コンパニオンは"virtualization"です。
単一の関数をテストするために、一時的なメモリ内の「実際の」DBインスタンスを起動できますか?はい ?そこでは、より良いテストがあります。保存および取得された実際のデータをチェックするテストです。
さて、あなたはユニットテストを統合テストに変えたと言うかもしれません。単体テストと統合テストの間で分類する線をどこに引くかについては、さまざまな見方があります。私見、「ユニット」は任意の定義であり、あなたのニーズに合うはずです。
.Netの世界では、テストプロジェクトを作成し、コーディングからデバッグ、ラウンドトリップを除いたUIをテストする方法としてテストを作成する習慣があります。これは私が開発するための効率的な方法です。ビルドごとにすべてのテストを実行することにはあまり興味がありませんでした(開発のワークフローが遅くなるため)。これは、より大きなチームにとっての有用性を理解しています。それでも、コードをコミットする前にすべてのテストを実行して合格にするというルールを作成できます(データベースが実際にヒットしているためにテストの実行に時間がかかる場合)。
データアクセスレイヤー(DAO)をモックアウトして実際にデータベースにアクセスしないと、好きな方法でコードを作成できて慣れるだけでなく、実際のコードベースの大部分が失われます。データアクセスレイヤーとデータベースを実際にテストしておらず、ふりをして、モックアップに多くの時間を費やしている場合、実際にコードをテストするためのこのアプローチの有用性を理解できません。 1つのテストで大きなものをテストするのではなく、小さなピースをテストしています。私のアプローチは統合テストの線に沿っている可能性があることを理解していますが、実際に統合テストを一度だけ書いた場合、モックを使用した単体テストは冗長な時間の無駄のようです。これは、開発とデバッグを行うための良い方法でもあります。
実際、しばらくの間、私はTDDおよびBehavior Driven Design(BDD)を認識しており、それを使用する方法について考えていましたが、ユニットテストをさかのぼって追加することは困難です。おそらく私は間違っていますが、データベースを含めてエンドツーエンドでより多くのコードをカバーするテストを書くことは、より多くのコードをカバーし、テストを書くためのより効率的な方法である、より完全で優先度の高いテストのように思えます。
実際、ドメイン固有言語(DSL)でエンドツーエンドをテストしようとするBehaviorn Driven Design(BDD)のようなものは、進むべき道だと思います。 .Netの世界にはSpecFlowがありますが、Cucumberのオープンソースとして始まりました。
データアクセスレイヤーをモックアウトし、データベースにヒットしないように書いたテストの真の有用性に本当に感心していません。返されたオブジェクトはデータベースにヒットせず、データも入力されませんでした。それは私が不自然な方法で模造しなければならなかった完全に空のオブジェクトでした。時間の無駄だと思います。
Stack Overflowによれば、実際のオブジェクトを単体テストに組み込むのが現実的でない場合、モッキングが使用されます。
https://stackoverflow.com/questions/2665812/what-is-mocking
「モッキングは主に単体テストで使用されます。テスト中のオブジェクトは他の(複雑な)オブジェクトに依存している場合があります。テストするオブジェクトの動作を分離するには、他のオブジェクトを実際のオブジェクトの動作をシミュレートするモックに置き換えます。これは、実際のオブジェクトを単体テストに組み込むことが実用的でない場合に役立ちます。」
私の主張は、開発者として何かをチェックインする前に、エンドツーエンド(Web UIからビジネスレイヤー、データアクセスレイヤー、データベース、往復)をコーディングする場合、この往復のフローをテストするということです。 UIを切り取り、このフローをテストからデバッグおよびテストすると、UI以外のすべてをテストし、UIが期待するものを正確に返します。私が残したのは、UIに必要なものを送信することだけです。
私の自然な開発ワークフローの一部である、より完全なテストがあります。私にとって、これは、実際のユーザー仕様をエンドツーエンドで可能な限りテストすることを網羅する、最も優先度の高いテストであるべきです。他にもっときめ細かいテストを作成したことがない場合は、少なくとも、希望する機能が機能していることを証明するもう1つの完全なテストがあります。
Stack Exchangeの共同創設者は、100%の単体テストカバレッジを持つことの利点について確信が持てません。私もそうではありません。データベースのモックを毎日維持するよりもデータベースにヒットする、より完全な「統合テスト」を行います。
Unit Tests
とIntegration Tests
は互いに直交です。彼らはあなたが構築しているアプリケーションについて異なる見方を提供します。通常両方必要です。しかし、どのような種類のテストが必要かは、時点によって異なります。
たいていの場合、Unit Tests
が必要です。ユニットテストはテストされているコードのごく一部に焦点を当てています-正確にunit
と呼ばれるものは読者に任されています。しかし、目的は簡単です:fastフィードバックをwhenとwhereのフィードバックコードbroke。とはいえ、実際のDBへの呼び出しはnonoであることは明らかです。
一方で、データベースのない厳しい条件下でのみ単体テストが可能なものもあります。おそらく、コードに競合状態があり、DBへの呼び出しがunique constraint
の違反をスローします。これは、実際にシステムを使用している場合にのみスローされます。ただし、これらの種類のテストは高価であり、unit tests
ほど頻繁に実行することはできません(実行したくない)。