web-dev-qa-db-ja.com

実装の詳細に連動しない動作の単体テスト

彼の講演で TDD、どこがうまくいかなかったのか 、イアンクーパーは、ケントベックの当初の意図をTDDのユニットテストの背後に押し出し(クラスのメソッドではなく、動作をテストするため)、テストを次のように結合しないように主張します実装。

一般的なサービスとリポジトリのセットを持つシステムでのsave X to some data sourceのような動作の場合、テストを実装の詳細に結合せずに、リポジトリを介してサービスレベルで一部のデータの保存を単体テストするにはどうすればよいですか(特定のメソッドを呼び出すような)?この種のカップリングを回避することは、実際には何らかの努力の価値がありませんか?

16
Andy Hunt

saving X to data source外部依存関係と通信することを意味するため、特定の例は通常、特定のメソッドが呼び出されたかどうかをチェックしてテストする必要があるケースです。したがって、テストする必要がある動作は通信は期待どおりに発生しています

しかし、これは悪いことではありません。アプリケーションとその外部依存関係の間の境界インターフェースは実装の詳細ではありませんです。実際、これらはシステムのアーキテクチャで定義されています。つまり、そのような境界は変更される可能性が低い(または変更が必要な場合は、最も頻度の低い種類の変更になります)。したがって、テストをrepositoryインターフェースに結合しても、あまり問題は発生しません(問題がある場合は、インターフェースがアプリケーションから責任を盗んでいないかどうかを検討してください)。

ここで、UI、データベース、およびその他の外部サービスから切り離された、アプリケーションのビジネスルールのみを検討します。これは、コードの構造と動作の両方を自由に変更できる必要がある場合です。これは、アプリケーションの全体的な動作に変更がない場合でも、テストと実装の詳細を組み合わせると、運用コードよりも多くのテストコードを変更しなければならない場所です。ここで、StateではなくInteractionをテストすることで、速度を上げることができます。

PS:状態または相互作用によるテストがTDDの唯一の真の方法であるかどうかを言うのは私の意図ではありません-私はそれが適切なツールを適切な仕事に使用することの問題であると信じています

8
MichelHenrich

その話の私の解釈は:

  • クラスではなくコンポーネントをテストします。
  • インターフェイスポートを介してコンポーネントをテストします。

それは話では述べられていませんが、アドバイスの想定されるコンテキストは次のようなものだと思います:

  • たとえば、ユーティリティライブラリやフレームワークではなく、ユーザー向けのシステムを開発しています。
  • テストの目標は、競争力のある予算内で可能な限り正常に配信することです。
  • コンポーネントは、C#/ Javaのような静的で型付けされた単一の成熟した言語で記述されています。
  • コンポーネントは、10000〜50000行程度です。 MavenまたはVSプロジェクト、OSGIプラグインなど.
  • コンポーネントは、単一の開発者、または密接に統合されたチームによって作成されます。
  • 六角形アーキテクチャ のような用語とアプローチに従っています
  • コンポーネントポートは、ローカル言語とその型システムをそのまま残し、http/SQL/XML/bytes/...に切り替えます。
  • すべてのポートをラップすることは、Java/C#の意味で型付きインターフェースであり、実装をスイッチテクノロジーに切り替えることができます。

したがって、コンポーネントをテストすることは、何かがまだ合理的にユニットテストと呼ばれる可能性がある最大のスコープです。これは、一部の人々、特に学者がこの用語を使用する方法とはかなり異なります。これは、典型的な単体テストツールチュートリアルの例とは異なります。ただし、ハードウェアテストではそのオリジンに一致します。ボードとモジュールはユニットテスト済みであり、ワイヤーとネジではありません。または、少なくとも、ねじをテストするために模擬ボーイングを構築しないでください...

それから外挿して、自分の考えのいくつかを投げ入れ、

  • すべてのインターフェースは、入力、出力、または(データベースのような)コラボレーターのいずれかになります。
  • あなた test 入力インターフェース;メソッドを呼び出し、戻り値をアサートします。
  • あなた mock 出力インターフェース;所定のテストケースに対して期待されるメソッドが呼び出されることを確認します。
  • あなた fake 協力者;シンプルだが実用的な実装を提供する

それを適切かつクリーンに行う場合、モックツールはほとんど必要ありません。システムごとに数回しか使用されません。

データベースは一般に共同作業者であるため、あざけるのではなく、偽造されます。これを手作業で実装するのは面倒です。幸いなことに、そのようなもの すでに存在

基本的なテストパターンは、一連の操作(ドキュメントの保存と再読み込みなど)を実行することです。動作することを確認します。これは他のテストシナリオと同じです。このようなテストが失敗する原因となる可能性のある(機能する)実装変更はありません。

例外は、データベースレコードが書き込まれるが、テスト中のシステムでは決して読み取られない場合です。例えば監査ログなど。これらは出力なので、モックする必要があります。テストパターンは、一連の操作を実行することです。指定されたメソッドと引数で監査インターフェースが呼び出されたことを確認します。

ここでも、 mockito のようなタイプセーフなモッキングツールを使用している場合、インターフェースメソッドの名前を変更してもテストエラーは発生しません。テストをロードした状態でIDEを使用すると、メソッドrenameとともにリファクタリングされます。使用しない場合、テストはコンパイルされません。

7
soru

私の提案は、状態ベースのテストアプローチを使用することです。

[〜#〜] given [〜#〜]テストDBは既知の状態にあります

[〜#〜] when [〜#〜]サービスは引数Xで呼び出されます

[〜#〜] then [〜#〜]読み取り専用リポジトリメソッドを呼び出して戻り値を確認することにより、DBが元の状態から予期された状態に変更されたことをアサートします

そうすることで、サービスの内部アルゴリズムに依存せず、テストを変更する必要なく、その実装を自由にリファクタリングできます。

ここでの唯一の結合は、DBからデータを読み取るために必要なサービスメソッド呼び出しとリポジトリ呼び出しへの結合です。

0
Elifarley