私たちのプロジェクトの方針は、単一クラスのみの単体テストを作成することです。すべての依存関係はモックされています。最近、このアプローチにより、次のような場合に脆弱になることがわかりました。
元々クラスA
は次のようになります:
class A(val b: B) {
fun doSomething() {
b.doSomethingElse()
b.doSomethingElse2()
}
}
そして、そのようなクラスA
は単体テストでカバーされています。次に、新しい要件により、クラスB
はリファクタリングを経てインターフェースの後ろに隠され、シナリオに基づいてクラスA
が異なる動作をすることが技術的に可能になります。
問題は、プロジェクトのガイドラインに従う必要があるときに、A
の単体テストもリファクタリングする必要があることです。以前は、B
のオブジェクトとの適切な通信のテストがありました(もちろん模倣されていました)。 B
への参照がなくなると、テストがリファクタリングされ、この新しいインターフェースとの通信が検証されます。
このシナリオは、このテストのリファクタリング中に情報の損失が発生しているためです。ユニットテストの純粋性により、A
-> B
間の通信が実際のシステムに存在する場合、検証を行わなくなりました。
それを念頭に置いて、単体テストの考え方を変えて、複数の実際のオブジェクトがあるテストケースを作成する必要がありますか?それはまだユニットテストですか、それともすでに統合テストですか?
ユニットの上限はIOです。周辺機器と話している場合、ユニットテストは不要です。
しかし、その範囲内で、必要に応じて厚くまたは細かく細かく切り分けることができます。
テストは3つのオブジェクトからメソッドを実行でき、それでも単体テストになります。これは統合だと主張する人もいますが、3行のコードは「統合」されているので、実際にはそうではありません。メソッドとオブジェクトの境界は、テストに大きな障壁を提示しません。周辺機器のこと。
次の場合、テストは単体テストではありません。
- データベースと通信します
- ネットワークを介して通信します
- それはファイルシステムに触れます
- 他の単体テストと同時に実行することはできません
- 実行するには、環境に特別なこと(構成ファイルの編集など)を行う必要があります。
確かに、ユニットテストフレームワークによって自動化できるすべてのテストをユニットテストと考える人もいると主張することができます。
意味論だけを主張したい場合は、悪臭のあるフィールドの美しい花としてユニットを定義します。
用語に関係なく、必要なのは、遅い問題のあるテストをテストから分離することです。非常に高速で安定しているため、入力中に実行できます。
彼らがあなたが何をするかについて彼らが何を行動するかを呼びますそれらを混同しないでください。
テストを2つのカテゴリーに分類するための代替アプローチ
-テストが遅い
-クイックテスト
通常、スローテストは、外部リソースへのアクセスを必要とするテストです。また、スローテストは、非常に非常に複雑な設定を必要とするテストになる場合があります。
クイックテストは、開発者が作業中に迅速なフィードバックを得るために使用するテストです。
Arseni Mourzenkoが彼の回答ですでに指摘したように、すべてのクラスを明示的にテストするか、消費者のテスト内で複数のクラスをテストするか、この決定はチームが行う必要があります。
プロダクションコードを記述する前にテストを記述する場合、複数のクラスをテストすることでワークフローがより迅速になり、より価値のあるフィードバックが提供されることがわかります。
いくつかの機能に取り掛かったとき、どのようなやり取りができるかまだわかりません。
したがって、テストでカバーされた1つのクラスまたは1つのメソッドにすべてを記述し、その後、重複または依存関係を抽出することによってリファクタリングします。
定義にとらわれないでください。それは重要なことではありません。
「ユニット」の正確な意味については意見の相違があります。ただし、自動テストの目的は、任意の定義に準拠することではありません。目的はバグを検出することです。またはより一般的には、コードの動作が期待どおりであることを確認します。
リファクタリングの際、単体テストでは、バグの導入や動作の変更がないことを確認する必要があります。したがって、テストはテストされたコードの実装の詳細と組み合わせるべきではありません。実際、クラスの実装を完全に書き直して、動作が同じであることをテストで確認できるようにする必要があります。
したがって、この観点から、テストは、テストされたクラスが他のクラスを呼び出すかどうかを気にしません。これは実装の詳細です。実際、典型的なリファクタリングは、メソッドの内部コードを別のクラスに抽出することです。これは、リファクタリングによって動作が変更されなかったことをテストで確認できるはずのシナリオです。
ただし、テストで単一のクラスのみをテストする場合は、テストと実装の詳細を組み合わせます。テストも変更してモックを導入する必要がある場合があるため、実装を安全にリファクタリングすることはできません。しかし、テストを変更すると同時にテスト済みコードを変更すると、以前と同じように検証することが確実ではないため、テストの目的全体が無効になります。
特に油断ができないのは、モックの特定のパラメーターを使用して特定のメソッドが呼び出されていることを確認するだけのモックです。この形式のテストは実装に非常に密接に関連しているため、ヘルプではなくリファクタリングの妨げになります。
つまり、モックを避け(外部サービスまたは非決定的入力の場合を除いて)、テストに必要な数のクラスを実行させて、テスト中の動作を検証します。
個人的には、「ユニット」を「動作のユニット」として考えるのが好きです-独立してテストできる仕様の最小部分。しかし、それは私の定義です。
リファクタリングされたクラスA
では、クラスB
と直接通信する必要はなくなり、代わりにInterface
と通信することになります。したがって、その通信をテストしている限り、特定のクラスB
と直接通信しないことを心配する必要はありません。
クラスA
は、クラスB
を何も認識していないため、A
のテストをクラス化する必要はありません。
クラスB
の動作をテストするには(クラスA
または他のクラスが実行時にクラスB
のインスタンスを呼び出す場合)、クラスB
に関する他のテストが必要です。
1つのテストでの多くのクラスの動作のテストは、まだユニットテストですか?
それは、単体テストの誰の定義が信頼できると見なされるかに依存します。
単体テストの考え方を変えて、実際のオブジェクトが複数あるテストケースを作成する必要がありますか。それはまだユニットテストですか、それともすでに統合テストですか?
後方互換性のない変更を行うと、変更内容に依存するものが壊れます。テストが今あなたに向かって叫んでいるなら、それは素晴らしいことです-すべてがそれが想定されている方法で機能しています。
この時点で、いくつかの選択肢があります。 1つは、デザインに加えた変更を再考して、下位互換性を保つことです。これは通常、新しいクラスとインターフェースを作成し、新しい要素の観点から古いユースケースを実装することを意味します。実際には、古いコードはリファクタリングされ、新しい方法で適切な方法で活用されます。テストでは、それを行う際に新しいミスが導入されていないことを確認します。新しい単体テストは、新しい動作をカバーしています。
補足:このステップでは、変更を管理する方法として古い方法で廃止できます。これにより、新しいものを追加することから古いものを削除することを時間内に分離できます。
もう1つの可能性は、この重大な変更が必要であると判断した場合であり、これがそのための費用を支払うのに適切な時期です。したがって、問題のテストを削除してください。たーだ!これで完了です。赤ちゃんとお風呂の両方の水を交換する場合は、完全に合理的な選択です。
問題のケースは真ん中にあります。 「小さな」互換性のない変更をコミットしていて、テストの値のmostがまだ存在しているが、その値が蓄積され続けると、不釣り合いな量が必要になる仕事の。
多くの場合、これは、テストが多くの不安定な決定( Parnas、1971 を参照)に及んでいること、または現在の設計が要素のスワッピングを非常に高価にしていることを意味します。
たとえば、振る舞いは単一のアトミックなものであると見せかけるのは簡単ですが、実際には実際にはそれは実際にはいくつかの異なる独立したアイデアの蓄積です。テストを分割して、(「クラス」のような構造的な問題を心配するのではなく)行動のアイデアに集中するようにすると、差し迫った問題以外の変更に対して弾力性のあるテストになります。
しかし、正直に言って、その状態に到達するには、いくつかの思考と設計をフロントローディングする必要があります。
James Shoreの モックなしのテスト ;を確認することをお勧めします。または プロパティベースのテスト に関するいくつかの公開された記事は、多くの場合、単一の動作を取り、それをいくつかの異なるテストに分割します。これにより、全体的な行動の変化。
過去の単体テストは、単一のコンポーネント、クラスの動作をテストしています。クラスが予想される動作を満たさなくなった回帰エラーをすぐに見つけるため。
しかし、今日ではユニットテストがはるかに多く使用されています。
テスト駆動開発
これは、動作を保証しながら、最初から分離/綿毛で部品を開発するための迅速な方法です。
データの整合性
ビジネスプロセスフローのXMLをチェックして、次の状態へのデフォルトの無条件フローが常に存在することを確認しました。これらのテストは、ハードデータリソースに起こり得る間違いが含まれていないことを保証します。いわば自分のチェック「コンパイラ」。同じことがいくつかの言語の翻訳を比較するために保持することができます。 (リソースファイルの場合、クラスパスディレクトリウォークを使用した単一の単体テストで、リソースディレクトリツリー内のファイルallファイルをテストしたので、将来のリソースは自動的にテストされます。)
すでにこれらのケースは、ユニットテストが実際に品質保証尺度として使用されていることを示しています。
したがって、コンポーネントコンテナとすべてのシステム動作をテストすることもできます。 テストのみのアイデアnits(ユニットの作成後)は廃止されました。