web-dev-qa-db-ja.com

(なぜ)単体テストが依存関係をテストしないことが重要ですか?

私は自動テストの価値を理解し、問題が十分に特定されていて、適切なテストケースを見つけられるところならどこでも使用します。ただし、ここやStackOverflowの一部の人は、テストの依存性ではなくユニットのみを強調していることに気づきましたonlyユニット。ここでは、メリットがわかりません。

依存関係のテストを回避するためのモック/スタブは、テストを複雑にします。モックをサポートするために、プロダクションコードに人工的な柔軟性/デカップリング要件を追加します。 (私はこれが優れたデザインを促進すると言っている人には同意しません。追加のコードの記述、依存関係注入フレームワークなどの導入、またはコードベースに複雑さを追加して、実際のユースケースなしで物事をより柔軟/プラグイン可能/拡張可能/分離することは、過剰設計であり、良いデザインです。)

第2に、依存関係のテストとは、あらゆる場所で使用される重要な低レベルコードが、そのテストを書いた人が明示的に考えたもの以外の入力でテストされることを意味します。依存する低レベルの機能をモックアウトせずに高レベルの機能で単体テストを実行したところ、低レベルの機能に多くのバグが見つかりました。理想的には、これらは低レベルの機能の単体テストによって検出されたはずですが、失敗したケースは常に発生します。

これの反対側は何ですか?単体テストで依存関係もテストしないことが本当に重要ですか?もしそうなら、なぜですか?

編集:データベース、ネットワーク、Webサービスなどのモック外部依存関係の値を理解できます(これを明確にするよう動機付けしてくれたAnna Learに感謝します) 。)内部依存関係、つまり直接的な外部依存関係を持たない他のクラス、静的関数などを参照していました。

107
dsimcha

それは定義の問題です。依存関係のあるテストは、単体テストではなく統合テストです。統合テストスイートも必要です。違いは、統合テストスイートは別のテストフレームワークで実行される可能性があり、ビルドに時間がかかるため、ビルドの一部ではない可能性があることです。

私たちの製品の場合:ユニットテストはビルドごとに数秒で実行されます。統合テストのサブセットは、チェックインごとに実行され、10分かかります。私たちの完全な統合スイートは毎晩実行され、4時間かかります。

118
Jeffrey Faust

依存関係をすべて備えたテストは依然として重要ですが、Jeffrey Faust氏が言ったように、統合テストの領域のほうが重要です。

単体テストの最も重要な側面の1つは、テストを信頼できるものにすることです。合格したテストが本当に良いことであり、不合格だったテストが本番コードに問題があることを本当に信用していない場合、テストはそれほど有用ではありません。

テストを信頼できるものにするためには、いくつかのことを行う必要がありますが、この回答では1つだけに焦点を当てます。すべての開発者がコードをチェックインする前に簡単に実行できるように、それらが簡単に実行できることを確認する必要があります。「実行が簡単」とは、テストが迅速に実行されることを意味しますand広範な構成やそれらを動かすために必要なセットアップ。理想的には、誰もが最新バージョンのコードをチェックアウトして、すぐにテストを実行し、それらがパスすることを確認できる必要があります。

他のもの(ファイルシステム、データベース、Webサービスなど)への依存関係を抽象化することで、構成が不要になり、あなたや他の開発者が「ああ、私はテストが失敗したので、テストに失敗しました」という状況の影響を受けにくくなります。ネットワーク共有を設定しました。まあ、後で実行します。」

一部のデータを使用して何を実行するかをテストする場合、そのビジネスロジックコードのユニットテストではhowを気にする必要はありません。そのデータを取得します。データベースなどのサポート機能に依存せずにアプリケーションのコアロジックをテストできることは素晴らしいことです。あなたがそれをしていなければ、あなたは逃しています。

追伸テスト可能性の名の下でオーバーエンジニアリングすることは間違いなく可能であると付け加えておきます。アプリケーションをテスト駆動すると、その軽減に役立ちます。しかし、いずれにしても、アプローチの実装が不十分であっても、アプローチの有効性が低くなるわけではありません。 「なぜ私はこれをしているのですか?」開発中。


public class MyClass 
{
    private SomeClass someClass;
    public MyClass()
    {
        someClass = new SomeClass();
    }

    // use someClass in some way
}

通常、SomeClassがどのように作成されるかは気にしません。使いたいだけです。 SomeClassが変更され、コンストラクタへのパラメータが必要になった場合...それは私の問題ではありません。これに対応するためにMyClassを変更する必要はありません。

さて、それはデザインの部分に触れているだけです。単体テストに関しては、他のクラスから身を守りたいです。私がMyClassをテストしている場合、外部依存関係がないこと、SomeClassがデータベース接続やその他の外部リンクを導入しなかったことを知っているのが好きです。

しかし、さらに大きな問題は、一部のメソッドの結果がSomeClassのいくつかのメソッドからの出力に依存していることも知っていることです。 SomeClassをモック/スタブ化しないと、その入力をオンデマンドで変更する方法がありません。運が良ければ、SomeClassからの正しい応答をトリガーするようにテスト内で環境を構成できるかもしれませんが、そのようにすると、テストが複雑になり、テストが脆弱になります。

コンストラクターでSomeClassのインスタンスを受け入れるようにMyClassを書き換えると、(モックフレームワークを介して、または手動のモックを使用して)必要な値を返すSomeClassの偽のインスタンスを作成できます。私は通常、この場合にインターフェースを導入するためにhaveを実行しません。そうするかどうかは、多くの点で、選択した言語によって決定される個人的な選択です(たとえば、C#ではインターフェイスの可能性が高くなりますが、Rubyでは必要ありません)。

39
Adam Lear

単体テストと統合テストの問題以外に、次の点を考慮してください。

クラスウィジェットは、ThingamajigおよびWhatsItクラスに依存しています。

ウィジェットの単体テストが失敗します。

どのクラスに問題がありますか?

「デバッガを起動する」または「コードが見つかるまでコードを最後まで読む」と答えた場合、依存関係ではなく、ユニットのみをテストすることの重要性を理解しています。

22
Brook

プログラミングが料理のようなものだと想像してみてください。単体テストは、食材が新鮮でおいしいなどを確認することと同じです。一方、統合テストは、食事がおいしいことを確認することと同じです。

究極的には、あなたの食事が美味しいこと(またはシステムが機能すること)を確認することが、最も重要なことであり、最終的な目標です。しかし、もしあなたの成分、つまり、単位、仕事があれば、より安価な方法で調べることができます。

実際、ユニット/メソッドが機能することを保証できれば、システムが機能している可能性が高くなります。 「確実」ではなく、「可能性が高い」ことを強調します。あなたが調理した食事を味わって最終製品が良いことを伝える誰かがまだ必要であるのと同じように、あなたはまだ統合テストが必要です。あなたは新鮮な食材を使ってそこに着くのが簡単になります、それだけです。

15
Julio

ユニットを完全に分離してユニットをテストすると、このユニットが送信できるデータと状況のすべての可能なバリエーションをテストできます。それは他から隔離されているので、テストされるユニットに直接影響を与えないバリエーションを無視することができます。これにより、テストの複雑さが大幅に減少します。

さまざまなレベルの依存関係を統合してテストすると、単体テストではテストされなかった特定のシナリオをテストできます。

どちらも重要です。単体テストを実行するだけで、コンポーネントを統合するときに常に複雑な微妙なエラーが発生します。統合テストを行うだけでは、マシンの個々のパーツがテストされていないという確信なしにシステムをテストしていることになります。統合テストを実行するだけでより完全なテストを達成できると考えるのは、エントリの組み合わせの数を追加するコンポーネントが増えるにつれて非常に速くなり(階乗を考えると)、十分なカバレッジでテストを作成することはすぐに不可能になるため、ほとんど不可能です。

つまり、ほぼすべてのプロジェクトで通常使用する3つのレベルの「単体テスト」:

  • 単体テスト。依存関係をモックまたはスタブ化して分離し、単一コンポーネントの地獄をテストします。理想的には完全なカバレッジを試みるでしょう。
  • より微妙なエラーをテストするための統合テスト。制限ケースと典型的な状態を示すスポットチェックを注意深く作成しました。多くの場合、完全にカバーすることは不可能であり、システムが失敗する原因に焦点を当てた取り組みが行われます。
  • システムにさまざまな実際のデータを注入し、データを計測し(可能な場合)、出力を観察して仕様とビジネスルールに準拠していることを確認する仕様テスト。

依存性注入は、システムを複雑にすることなく、ユニットテスト用のコンポーネントを非常に簡単に分離できるため、これを実現するための非常に効率的な手段の1つです。あなたのビジネスシナリオは注入メカニズムの使用を保証しないかもしれませんが、あなたのテストシナリオはほとんどそうです。これは私にとっては不可欠なものにするのに十分です。それらを使用して、部分的な統合テストを介して、異なる抽象化レベルを個別にテストすることもできます。

6
Newtopian

これの反対側は何ですか?単体テストで依存関係もテストしないことが本当に重要ですか?もしそうなら、なぜですか?

単位。単数を意味します。

2つのものをテストするということは、2つのものがあることを意味しますandすべての機能依存関係。

3番目のものを追加すると、テストの機能の依存関係が線形を超えて増加します。モノの相互接続はモノの数よりも速く成長します。

テスト中のn項目の間にn(n-1)/ 2の潜在的な依存関係があります。

それが大きな理由です。

シンプルさには価値があります。

4
S.Lott

最初に再帰を習得した方法を覚えていますか?私の教授は、「xを実行するメソッドがあると仮定します」(たとえば、任意のxのフィボナッチを解く)と言いました。 「xを解くには、そのメソッドをx-1とx-2で呼び出す必要があります」。同じ点で、依存関係をスタブ化することで、依存関係が存在するふりをして、現在のユニットが実行する必要があることをテストできます。もちろん、依存関係を厳密にテストしていることを前提としています。

これは本質的には作業中のSRPです。あなたのテストに対しても単一の責任に集中することで、あなたがしなければならないメンタルジャグリングの量を取り除きます。

2
Michael Brown

(これはマイナーな回答です。ヒントを提供してくれた@TimWilliscroftに感謝します。)

フォールトはlocalizeの方が簡単です:

  • テスト対象のユニットとその依存関係の両方が個別にテストされます。
  • 各テストは、テストの対象となるコードに障害がある場合にのみ失敗します。

これは紙の上でうまく動作します。ただし、OPの説明(依存関係にバグがある)に示されているように、依存関係がテストされていない場合、障害の場所を特定するのは困難です。

2
rwong

良い答えがたくさん。他にもいくつかポイントを追加します。

単体テストでは、依存関係が存在しない場合にコードをテストすることもできます。例えば。あなたやあなたのチームはまだ他のレイヤーを書いていないか、多分あなたは他の会社が提供するインターフェースを待っているでしょう。

ユニットテストは、開発マシンに完全な環境(データベース、ウェブサーバーなど)を用意する必要がないことも意味します。すべての開発者doがそのような環境を持っていることを強くお勧めします。ただし、バグを再現するために、それを削減します。ただし、何らかの理由で本番環境を模倣することができない場合は、ユニットテストを行うことで、コードが大規模なテストシステムに移行する前に、コードにある程度の信頼を与えることができます。

2
Steve

ええと... ここでこれらの答えに書かれたユニットと統合テストに良い点があります!

コスト関連および実用的な見方はここにありません。とは言っても、非常に分離された/原子的な単体テスト(おそらく相互に非常に独立しており、データベースやファイルシステムなどの依存関係なしに並行して実行するオプションがある)と(より高いレベルの)統合テストのメリットがはっきりとわかります。しかし...それはコスト(時間、お金など)とリスクの問題でもあります。

だから、あなたが考える前にはるかに重要な他の要因があります(「何をテストするか)」私の経験からの「テスト方法」...

私の顧客は(暗黙的に)追加のテストの作成と保守の費用を支払いますか?よりテスト主導のアプローチ(コードを記述する前に最初にテストを作成する)は、私の環境(コード障害のリスク/コスト分析、人、設計仕様、テスト環境のセットアップ)で本当にコスト効率が良いですか?コードには常にバグがありますが、テストを本番環境での使用に移行する方が(最悪の場合!)より費用効果が高くなるでしょうか?

また、コードの品質(標準)が何か、フレームワーク、IDE、設計原則など、ユーザーとチームが従うもの、およびそれらの経験に大きく依存します。よく書かれ、簡単に理解でき、十分に文書化されています(理想的には自己文書化)、モジュラー、...コードは、逆の場合よりもバグが少ない可能性があります。そのため、実際の「必要性」、圧力、または全体的なメンテナンス/バグ修正コスト/広範なテストのリスクは高くない可能性があります。

私のチームの同僚が提案した極端なところに行きましょう。私たちは純粋なJava EEモデルレイヤーコードをユニットテストして、データベースのモックを作成します。または、統合テストをすべての可能な現実世界のユースケースとWeb UIワークフローの100%でカバーすることを望むマネージャーは、ユースケースが失敗するリスクを冒したくないためですが、予算は限られています。約100万ユーロ、すべてをコード化するためのかなり厳しい計画です。潜在的なアプリのバグが人間や企業にとってそれほど大きな危険ではない顧客環境。このアプリは、(一部の)重要な単体テスト、統合テストで内部テストされます、設計されたテスト計画、テストフェーズなどを使用した主要な顧客テスト。一部の原子力工場や医薬品製造用のアプリは開発していません!(指定したデータベースが1つだけあり、開発者ごとにクローンを簡単にテストでき、webappと密に結合していますまたはモデルレイヤー)

私自身も、可能であればテストを記述し、コードの単体テスト中に開発を試みます。しかし、私は多くの場合、トップダウンアプローチ(統合テスト)からそれを行い、重要なテスト(多くの場合、モデルレイヤー)の「アプリレイヤーカット」をどこに作成するかという良い点を見つけようとします。 (それは多くの場合「レイヤー」について多くあるため)

さらに、単体テストと統合テストのコードは、時間、費用、メンテナンス、コーディングなどに悪影響を与えません。クールで適用する必要がありますが、開発中のテストの開始時または5年後の影響を考慮して慎重に検討してくださいコード。

だから、私はそれを本当に言います信頼とコスト/リスク/利益評価に大きく依存します ...現実生活のように、100でたくさん走ることができず、走りたくない%安全メカニズム。

子供はどこかに登ることができ、登るべきであり、転倒して自分を傷つける可能性があります。間違った燃料を入れたため、車が動かなくなる可能性があります(無効な入力:))。 3年後に時間ボタンが機能しなくなった場合、トーストが焦げる可能性があります。しかし、私は決して高速道路を走りたくないし、私の切り離されたステアリングホイールを私の手で保持したくありません:)

0

設計面について:小規模なプロジェクトでも、コードをテスト可能にすることにはメリットがあると思います。必ずしもGuiceのようなものを導入する必要はありませんが(単純なファクトリクラスがしばしばそうします)、プログラミングロジックから構築プロセスを分離すると、

  • インターフェイスを介してすべてのクラスの依存関係を明確に文書化します(チームの新人には非常に役立ちます)
  • クラスがより明確になり、保守しやすくなります(醜いオブジェクトグラフの作成を別のクラスに入れると)。
  • 疎結合(変更をはるかに容易にする)
0
Oliver Weiler