スタブとすべてを公開する単体テストのポイントはありますか?


59

「適切な」方法でユニットテストを行う場合、つまり、すべてのパブリックコールをスタブ化し、プリセット値またはモックを返す場合、実際には何もテストしていないように感じます。文字通り、コードを見て、パブリックメソッドを通るロジックのフローに基づいて例を作成しています。そして、実装が変更されるたびに、それらのテストを行って変更する必要があります。これは、(中期でも長期でも)何か有用なことを達成しているとは感じません。また、統合テスト(非幸福なパスを含む)も行いますが、テスト時間が長くなることはあまり気にしません。それらで、私は実際に回帰をテストしているように感じます。なぜなら、それらは複数をキャッチしたからです。一方、ユニットテストは、パブリックメソッドの実装が変更されたことを示しています。

単体テストは広大なトピックであり、私はここで何かを理解していないと感じています。単体テストと統合テストの決定的な利点は何ですか(時間のオーバーヘッドを除く)。


4
私の2セント:モックを使いすぎないでください(googletesting.blogspot.com/2013/05/…–
フアンメンデス

回答:


37

「適切な」方法でユニットテストを行う場合、つまり、すべてのパブリックコールをスタブ化し、プリセット値またはモックを返す場合、実際には何もテストしていないように感じます。文字通り、コードを見て、パブリックメソッドを通るロジックのフローに基づいて例を作成しています。

これは、テストするメソッドが他のいくつかのクラスインスタンス(モックする必要がある)を必要とし、それ自体でいくつかのメソッドを呼び出すように聞こえます。

このタイプのコードは、あなたが概説した理由により、実際にユニットテストするのは困難です。

私が役立ったのは、そのようなクラスを次のように分割することです。

  1. 実際の「ビジネスロジック」を持つクラス。これらは、他のクラスへの呼び出しをほとんどまたはまったく使用せず、テストが容易です(値の入力-値の出力)。
  2. 外部システム(ファイル、データベースなど)とインターフェースするクラス。これらは外部システムをラップし、ニーズに合った便利なインターフェースを提供します。
  3. 「すべてを結びつける」クラス

その後、1のクラスは、値を受け入れて結果を返すだけなので、ユニットテストが簡単です。より複雑な場合、これらのクラスは独自に呼び出しを実行する必要がありますが、2からのみクラスを呼び出します(データベース関数などを直接呼び出しません)。2からのクラスはモックが簡単です(なぜならラップされたシステムの必要な部分を公開します)。

2.と3.のクラスは、通常、意味のある単体テストを行うことはできません(独自に有用なことは何もしないため、単に「接着」コードです)。OTOH、これらのクラスは比較的単純(および少数)になる傾向があるため、統合テストで適切にカバーする必要があります。


ワンクラス

データベースから価格を取得し、割引を適用してからデータベースを更新するクラスがあるとします。

これをすべて1つのクラスに含める場合は、模擬するのが難しいDB関数を呼び出す必要があります。擬似コードで:

1 select price from database
2 perform price calculation, possibly fetching parameters from database
3 update price in database

3つのステップすべてにDBアクセスが必要になるため、多くの(複雑な)モックが必要になります。これは、コードまたはDB構造が変更されると壊れる可能性があります。

分割する

3つのクラスに分割します:PriceCalculation、PriceRepository、App。

PriceCalculationは実際の計算のみを行い、必要な値を提供します。アプリはすべてを結び付けます:

App:
fetch price data from PriceRepository
call PriceCalculation with input values
call PriceRepository to update prices

その方法:

  • PriceCalculationは、「ビジネスロジック」をカプセル化します。それ自体では何も呼び出さないため、テストは簡単です。
  • PriceRepositoryは、模擬データベースを設定し、読み取り呼び出しと更新呼び出しをテストすることにより、疑似ユニットテストを行うことができます。ロジックが少ないため、コードパスが少ないため、これらのテストをあまり必要としません。
  • アプリはグルーコードであるため、意味のある単体テストはできません。ただし、これも非常に単純なので、統合テストで十分です。後でアプリが複雑になりすぎると、より多くの「ビジネスロジック」クラスが発生します。

最後に、PriceCalculationが独自のデータベース呼び出しを行う必要があることが判明する場合があります。たとえば、PriceCalculationだけがどのデータが必要かを知っているため、Appによって事前に取得することはできません。次に、PriceCalculationのニーズに合わせてカスタマイズされたPriceRepository(または他のリポジトリクラス)のインスタンスを渡すことができます。PriceRepositoryのインターフェースがシンプルであるため、例えば、このクラスは、嘲笑する必要がありますが、これはシンプルになりますPriceRepository.getPrice(articleNo, contractType)。最も重要なことは、PriceRepositoryのインターフェースがPriceCalculationをデータベースから隔離しているため、DBスキーマまたはデータ編成の変更がインターフェースを変更する可能性が低いため、モックが破られる可能性が低いことです。


5
私は、おかげで、私はすべてのテストユニット内のポイントを見ていないだけではと思った
enthrops

4
タイプ3のクラスが少数であると言うとき、私は同意しません。私のコードのほとんどはタイプ3であり、ビジネスロジックはほとんどないように感じます。:これは私が何を意味するかであるstackoverflow.com/questions/38496185/...
ロドリゴ・ルイス

27

単体テストと統合テストの決定的な利点は何ですか?

それは間違った二分法です。

単体テストと統合テストは、2つの類似した、しかし異なる目的に役立ちます。単体テストの目的は、メソッドが機能することを確認することです。実際には、ユニットテストは、コードがユニットテストで概説された契約を満たしていることを確認します。 これは、ユニットテストの設計方法から明らかです。ユニットテストは、コードが何を行うべきかを具体的に述べ、コードがそれを行うと断言します。

統合テストは異なります。統合テストは、ソフトウェアコンポーネント間の相互作用を実行します。すべてのテストに合格しても、適切に相互作用しないために統合テストに失敗するソフトウェアコンポーネントを持つことができます。

ただし、単体テストに決定的な利点がある場合、これは次のとおりです。単体テストはセットアップがはるかに簡単であり、統合テストよりもはるかに少ない時間と労力で済みます。 ユニットテストを適切に使用すると、「テスト可能な」コードの開発が促進されます。つまり、最終結果の信頼性、理解、および保守が容易になります。テスト可能なコードには、一貫したAPI、反復可能な動作などの特定の特性があり、簡単にアサートできる結果を返します。

複雑なモック、複雑なセットアップ、および困難なアサーションが必要になることが多いため、統合テストはより難しく、より高価です。システム統合の最高レベルで、UIで人間の相互作用をシミュレートしようとすることを想像してください。ソフトウェアシステム全体が、そのような自動化に専念しています。そして、私たちが求めているのは自動化です。人間のテストは再現性がなく、自動テストのように拡張できません。

最後に、統合テストではコードカバレッジが保証されません。統合テストでテストしているコードループ、条件、ブランチの組み合わせはいくつありますか?本当に知っていますか?ユニットテストとテスト対象のメソッドで使用できるツールがあり、コードカバレッジの量、およびコードの循環的な複雑さを確認できます。しかし、それらは、単体テストが存在するメソッドレベルでしかうまく機能しません。


リファクタリングするたびにテストが変わる場合、それは別の問題です。単体テストは、ソフトウェアが実行することを文書化し、それを実行することを証明し、基礎となる実装をリファクタリングするときに再度実行することを証明することを想定しています。APIが変更された場合、またはシステム設計の変更に応じてメソッドを変更する必要がある場合、それが起こるはずです。頻繁に発生する場合は、コードを書く前に、まずテストを書くことを検討してください。 これにより、全体的なアーキテクチャについて考えるようになり、既に確立されたAPIでコードを書くことができます。

次のような簡単なコードの単体テストを書くのに多くの時間を費やしている場合

public string SomeProperty { get; set; }

その後、アプローチを再検討する必要があります。単体テストは動作をテストするためのものであり上記のコード行には動作がありません。ただし、コード内の依存関係はどこかで作成されています。そのプロパティは、ほぼ確実にコードの他の場所で参照されるからです。それを行う代わりに、必要なプロパティをパラメーターとして受け入れるメソッドを記述することを検討してください。

public string SomeMethod(string someProperty);

これで、メソッドはそれ自体の外側の何かに依存しなくなり、完全に自己完結型になったため、よりテストしやすくなりました。確かに、あなたはいつもこれを行うことができるとは限りませんが、よりテストしやすい方向にコードを移動し、今回は実際の動作の単体テストを書いています


2
ユニットテストと統合テストサーバーの目標は異なることは承知していますが、ユニットテストが行​​うすべてのパブリックコールをスタブおよびモックすると、ユニットテストがどのように役立つかがわかりません。スタブやモック用でない場合、「コードは単体テストで概説された契約を満たしている」と理解します。私の単体テストは、文字通り、テストしているメソッド内のロジックを反映しています。あなた(I)は実際には何もテストしておらず、単にコードを見て、それをテストに「変換」しています。自動化の難易度とコードカバレッジについては、現在Railsをやっており、これらは両方ともうまく処理されています。
人類

2
テストがメソッドロジックの単なる反映である場合、それは間違っています。ユニットテストでは、基本的にメソッドに値を渡し、戻り値を受け入れ、その戻り値がどうあるべきかについてアサーションを行う必要があります。そのためのロジックは必要ありません。
ロバートハーベイ

2
理にかなっていますが、他のすべてのパブリックコール(db、現在のユーザーステータスなどの「グローバル」など)をすべてスタブ化し、メソッドロジックに従ってコードをテストする必要があります。
人類

1
だから私は、ユニットテストは、「入力のセット->期待される結果のセット」を確認するような、ほとんど孤立したものに対するものだと思いますか?
人類

1
多くの単体テストと統合テストを作成した経験(これらのテストで使用される高度なモック、統合テスト、コードカバレッジツールは言うまでもありません)は、ここでの主張のほとんどと矛盾しています。1)単体テストの目的は、コードは想定どおりに機能します」:同じことが統合テストにも当てはまります(さらにそうです)。2)「単体テストの設定ははるかに簡単です」:いいえ、そうではありません(多くの場合、統合テストの方が簡単です)。3)「適切に使用すると、単体テストは「テスト可能な」コードの開発を促進します」:統合テストと同じ。(続き)
ロジェリオ

4

モックを使用した単体テストでは、クラスの実装が正しいことを確認します。テストするコードの依存関係のパブリックインターフェイスをモックします。この方法により、クラスの外部すべてを制御でき、テストの失敗は他のオブジェクトのいずれかではなく、クラスの内部の何かに起因することが確実になります。

また、実装ではなく、テスト対象のクラスの動作をテストしています。コードをリファクタリングする(新しい内部メソッドを作成するなど)場合、ユニットテストは失敗しません。ただし、パブリックメソッドの動作を変更する場合、動作を変更したため、テストは絶対に失敗します。

また、コードを書いた後にテストを書いているように聞こえますが、代わりに最初にテストを書いてみてください。クラスに必要な動作の概要を説明してから、テストに合格するための最小限のコードを記述してください。

単体テストと統合テストの両方が、コードの品質を保証するのに役立ちます。単体テストでは、各コンポーネントを個別に調べます。また、統合テストでは、すべてのコンポーネントが適切に相互作用することを確認します。テストスイートに両方のタイプが必要です。

ユニットテストは、一度に1つのアプリケーションに集中できるため、開発に役立ちました。まだ作成していないコンポーネントのモック。彼らはまた、私が発見したロジックのバグを(ユニットテストでさえ)文書化するので、回帰にも最適です。

更新

メソッドが呼び出されることを確認するだけのテストを作成することには、メソッドが実際に呼び出されることを確認するという意味があります。特に、最初にテストを作成する場合は、実行する必要があるメソッドのチェックリストがあります。このコードはほとんど手続き型であるため、メソッドが呼び出されること以外は確認する必要はほとんどありません。将来の変更のためにコードを保護しています。あるメソッドを他のメソッドの前に呼び出す必要がある場合。または、初期メソッドが例外をスローした場合でも、メソッドが常に呼び出されること。

このメソッドのテストは変更されないか、メソッドを変更するときにのみ変更される場合があります。なぜこれが悪いのですか?テストの使用を強化するのに役立ちます。コードを変更した後にテストを修正する必要がある場合、コードでテストを変更する習慣がつきます。


そして、メソッドがプライベートなものを呼び出さない場合、それをユニットテストする意味はありません、正しいですか?
人類

メソッドがプライベートの場合、明示的にテストするのではなく、パブリックインターフェイスを介してテストする必要があります。すべてのパブリックメソッドをテストして、動作が正しいことを確認する必要があります。
シュライス

いいえ、パブリックメソッドがプライベートを呼び出さない場合、そのパブリックメソッドをテストすることは意味がありますか?
人道主義

はい。メソッドは何かしませんか?したがって、テストする必要があります。テストの観点からは、プライベートなものを使用しているかどうかはわかりません。入力Aを提供する場合、出力Bを取得する必要があることを知っています
。-シュライス

そうそう、メソッドは何かを実行し、その何かは他のパブリックメソッドへの呼び出しです(そしてそれだけです)。したがって、いくつかの戻り値を使用して呼び出しをスタブするテストを「適切に」行い、メッセージの期待値を設定します。この場合、正確に何をテストしていますか?適切な呼び出しが行われていること?さて、あなたはそのメソッドを書いたので、それを見て、それが何をするのかを正確に見ることができます。「入力->出力」のように使用することになっている分離メソッドには、ユニットテストの方が適していると思います。そのため、リファクタリング時に多数の例をセットアップし、回帰テストを実行できます。
人道主義

3

私は同様の質問を経験していました-コンポーネントテストの力を発見するまで。要するに、デフォルトではモックせず、実際のオブジェクトを使用するという点を除いて、単体テストと同じです(依存性注入を使用することが理想的です)。

これにより、コードカバレッジが良好な堅牢なテストをすばやく作成できます。モックを常に更新する必要はありません。100%のモックを使用した単体テストよりも精度は少し劣るかもしれませんが、時間とお金を節約することでそれを補います。モックまたはフィクスチャを本当に使用する必要があるのは、ストレージバックエンドまたは外部サービスだけです。

実際、過度のモックはアンチパターンです。TDDアンチパターンモックは悪です。


0

opはすでに答えを示していますが、ここに2セントを追加しています。

単体テストと統合テストの決定的な利点は何ですか(時間のオーバーヘッドを除く)。

また、

「適切な」方法でユニットテストを行う場合、つまり、すべてのパブリックコールをスタブ化し、プリセット値またはモックを返す場合、実際には何もテストしていないように感じます。

OPが尋ねたものの、正確ではありませんが有用です:

単体テストは動作しますが、まだバグがありますか?

スイートのテストに関する私の小さな経験から、ユニットテストは常にクラスの最も基本的なメソッドレベルの機能をテストするものであることを理解しています。私の意見では、パブリック、プライベート、または内部のすべてのメソッドには、専用のユニットテストが必要です。最近の経験でも、他の小さなプライベートメソッドを呼び出すパブリックメソッドがありました。そのため、2つのアプローチがありました。

  1. プライベートメソッドの単体テストを作成しないでください。
  2. プライベートメソッドの単体テストを作成します。

論理的に考えると、プライベートメソッドを持つことのポイントは次のとおりです。メインのパブリックメソッドが大きくなりすぎているか、面倒です。これを解決するには、賢明にリファクタリングし、個別のプライベートメソッドにふさわしい小さなコードチャンクを作成します。これにより、メインのパブリックメソッドがより小さくなります。このプライベートメソッドは後で再利用される可能性があることに留意して、リファクタリングします。そのプライベートメソッドに依存する他のパブリックメソッドはないが、将来を知っている場合があります。


プライベートメソッドが他の多くのパブリックメソッドによって再利用される場合を考慮します。

したがって、アプローチ1を選択した場合、パブリックメソッドとプライベートメソッドの各ブランチのユニットテストの数があったため、ユニットテストを複製して複雑になっていたでしょう。

アプローチ2を選択した場合、単体テスト用に記述されたコードは比較的少なくなり、テストがはるかに簡単になります。


プライベートメソッドが再利用されない場合を考えると、 そのメソッドの個別のユニットテストを書く意味はありません

用として統合テストを懸念している、彼らはハイレベルの網羅とよりになる傾向があります。彼らは、入力が与えられると、すべてのクラスがこの最終結論に到達する必要があることを伝えます。統合テストの有用性についてさらに理解するには、言及されたリンクを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.