Mockito.verify()を使用する場合


201

私は3つの目的でjUnitテストケースを作成します。

  1. すべての(またはほとんどの)入力の組み合わせ/値の下で、コードが必要な機能のすべてを確実に満たすようにします。
  2. 実装を変更できることを確認し、JUnitテストケースに依存して、すべての機能がまだ満足していることを通知します。
  3. 私のコードが扱うすべてのユースケースのドキュメントとして、コードをリライトする必要がある場合に備えて、リファクタリングの仕様として機能します。(コードをリファクタリングし、私のjUnitテストが失敗した場合-おそらくいくつかのユースケースを見逃していたでしょう)。

なぜ、いつMockito.verify()使うべきかわかりません。verify()呼び出されているのを見ると、私のjUnitが実装を認識していることがわかります。(したがって、私の機能に影響がなかったとしても、私の実装を変更すると、jUnitsが壊れてしまいます)。

を探しています:

  1. の適切な使用のためのガイドラインはMockito.verify()何ですか?

  2. jUnitsがテスト対象のクラスの実装を認識または密接に結合することは基本的に正しいですか?


1
公開したのと同じ理由で、verify()をできるだけ使用しないようにしていますが(ユニットテストで実装を認識させたくない)、選択の余地がない場合があります。 -スタブ化されたvoidメソッド。一般的に言えば、それらは何も返さないため、「実際の」出力に貢献しません。それでも、それが呼び出されたことを知る必要があります。しかし、私はあなたに同意します。検証を使用して実行フローを検証することは意味がありません。
Legna

回答:


78

クラスAのコントラクトにタイプCのオブジェクトのメソッドBを呼び出すという事実が含まれている場合は、タイプCのモックを作成し、メソッドBが呼び出されていることを確認することでこれをテストする必要があります。

これは、クラスAのコントラクトには、タイプC(インターフェイスまたはクラスの場合がある)について説明する十分な詳細があることを意味します。つまり、「システム要件」だけでなく、実装を説明するための何らかのレベルの仕様について話しているのです。

これは単体テストでは正常です。ユニットテストを行う場合、各ユニットが「正しいこと」を実行していることを確認する必要があります。これには通常、他のユニットとの相互作用が含まれます。ここでの「ユニット」は、クラス、またはアプリケーションのより大きなサブセットを意味する場合があります。

更新:

これは検証だけでなく、スタブにも当てはまると思います。コラボレータークラスのメソッドをスタブに入れるとすぐに、ユニットテストは、ある意味では、実装に依存するようになります。単体テストの性質上そうです。Mockitoは検証に関するものと同じくらいスタブに関するものであるため、Mockitoをまったく使用しているという事実は、この種の依存関係に遭遇することを意味します。

私の経験では、クラスの実装を変更すると、それに合わせてユニットテストの実装を変更しなければならないことがよくあります。一般的に、しかし、私はそこにどのようなユニットテストの在庫変更する必要はありませんされているクラスのために。もちろん、変更の理由は、以前にテストできなかった状態の存在でした。

つまり、これがユニットテストの目的です。コラボレータークラスの使用方法に対するこの種の依存性の影響を受けないテストは、実際にはサブシステムテストまたは統合テストです。もちろん、これらもJUnitで頻繁に記述され、モックの使用を伴うことがよくあります。私の意見では、「JUnit」はひどい名前であり、さまざまな種類のテストをすべて生成できる製品の名前です。


8
ありがとう、David。いくつかのコードセットをスキャンした後、これは一般的な方法のように見えますが、私にとっては、これは単体テストを作成する目的に反し、非常に小さな値のためにそれらを維持するオーバーヘッドを追加するだけです。モックが必要な理由と、テストを実行するための依存関係を設定する必要がある理由を理解しています。しかし、私の意見では、dependencyA.XYZ()メソッドが実行されていることを確認すると、テストが非常に脆弱になります。
ラッセル

@Russell「タイプC」がライブラリまたはアプリケーションの異なるサブシステムのラッパーのインターフェイスである場合でも、
Dawood ibnカリーム2012

1
サブシステムまたはサービスが呼び出されたことを確認するのに完全に役に立たないとは言いません。それに関するガイドラインがいくつかあるだけです(それらを作成することが私がやりたかったことです)。例:(私はおそらくそれを単純化しすぎています)私のコードでStrUtil.equals()を使用していて、実装でStrUtil.equalsIgnoreCase()に切り替えることにしました.jUnitがverify(StrUtil.equals )、実装は正確ですが、テストが失敗する可能性があります。これはライブラリー/サブシステム用ですが、この検証呼び出し、IMOは悪い習慣です。一方、検証を使用してcloseDbConnの呼び出しが有効なユースケースであることを確認します。
ラッセル

1
私はあなたを理解し、完全に同意します。しかし、あなたが説明するガイドラインを書くことは、TDDやBDDの教科書全体を書くことにも拡張できると私は感じています。例を挙げれば、equals()またはequalsIgnoreCase()を呼び出すと、クラスの要件で指定されたものにならないので、単体テスト自体は決してありません。ただし、「実行時にDB接続を閉じる」(これは実装に関してどのような意味でも)は、「ビジネス要件」ではなくても、クラスの要件である可能性があります。私にとって、これは契約間の関係に
帰着し

...ビジネス要件で表現されたクラス、およびそのクラスを単体テストする一連のテストメソッド。この関係を定義することは、TDDまたはBDDに関する本では重要なトピックです。Mockitoチームの誰かが自分のwikiについてこのトピックに関する投稿を書くことができたのに対し、他の多くの入手可能な文献とどのように異なるかわかりません。それがどのように異なるかがわかる場合は、私に知らせてください。おそらく、一緒に取り組むことができます。
Dawood ibnカリーム2012

60

もちろん、Davidの答えは正しいですが、なぜこれが必要なのかは十分に説明されていません。

基本的に、ユニットテストでは、機能のユニットを分離してテストします。入力が期待される出力を生成するかどうかをテストします。場合によっては、副作用もテストする必要があります。一言で言えば、検証はそれを行うことを許可します。

たとえば、DAOを使用して物事を保存することになっているビジネスロジックが少しあります。これを行うには、DAOをインスタンス化し、ビジネスロジックにフックし、データベースを調べて、予期したものが格納されているかどうかを確認する統合テストを使用します。それはもはやユニットテストではありません。

または、DAOをモックして、期待どおりに呼び出されることを確認することもできます。mockitoを使用すると、何かが呼び出されているか、どのくらいの頻度で呼び出されているかを確認でき、パラメーターでマッチャーを使用して、特定の方法で呼び出されることを確認できます。

このような単体テストの裏側は、実際にテストを実装に結び付けているため、リファクタリングが少し難しくなっています。一方、良いデザインの匂いは、適切に機能させるために必要なコードの量です。テストを非常に長くする必要がある場合は、おそらく設計に問題があります。したがって、テストが必要な副作用/複雑な相互作用が多いコードは、おそらく良いものではありません。


29

これはすばらしい質問です。その根本的な原因は次のとおりだと思います。私たちはユニットテストだけでなくJUnitを使用しています。したがって、質問は分割する必要があります。

  • 統合(または他のユニット以上のテスト)テストでMockito.verify()を使用する必要がありますか?
  • ブラックボックスユニットテストでMockito.verify()を使用する必要がありますか?
  • ホワイトボックスの単体テストでMockito.verify()を使用する必要がありますか?

したがって、ユニット以上のテストを無視する場合は、質問を言い換えることができます。「Mockito.verify()でホワイトボックスユニットテストを使用すると、ユニットテストと実装の間に素晴らしいカップルが作成されます。 「ユニットテストと、これに使用する経験則

それでは、このすべてを段階的に見ていきましょう。

*- 統合テスト(またはユニットテスト以上のテスト)でMockito.verify()を使用する必要がありますか?*答えは明らかにノーだと思います。さらに、これにはモックを使用しないでください。テストは、実際のアプリケーションにできるだけ近いものにする必要があります。アプリケーションの分離された部分ではなく、完全なユースケースをテストしています。

* ブラックボックスホワイトボックスの単体テスト * ブラックボックスアプローチを使用している場合、実際に何をしているのか(すべての等価クラス)の入力、状態、および期待される出力を受け取るテストを指定します。このアプローチでは、モックを使用することは一般的に正当化されますが(モックが正しいことを模倣するだけであり、テストしたくない)、Mockito.verify()の呼び出しは不必要です。

あなたが実際に何をしているのかホワイトボックスアプローチを使用している場合は、ユニットの動作をテストしています。このアプローチでは、Mockito.verify()の呼び出しが不可欠です。ユニットが期待どおりに動作することを確認する必要があります。

グレーボックステスト経験則ホワイトボックステストの問題は、それが高い結合を作成することです。1つの可能な解決策は、ホワイトボックステストではなく、グレーボックステストを行うことです。これは一種のブラック&ホワイトボックステストの組み合わせです。ホワイトボックステストのように、実際にユニットの動作をテストしていますが、一般的には、可能な場合は実装に依存しないようにします。可能な場合は、ブラックボックスの場合と同様にチェックを行い、出力が期待どおりであることを表明します。だから、あなたの質問の本質はそれが可能なときです。

これは本当に難しいです。良い例はありませんが、例を挙げましょう。上記のequals()とequalsIgnoreCase()で述べたケースでは、Mockito.verify()を呼び出さず、出力をアサートするだけです。それができなかった場合は、できるまでコードを小さな単位に分解してください。一方、いくつかの@Serviceがあり、@ Serviceの本質的にラッパーである@ Web-Serviceを記述しているとすると、@ Serviceへのすべての呼び出しが委任されます(追加のエラー処理が行われます)。この場合、Mockito.verify()の呼び出しは必須です。@ Seriveに対して行ったすべてのチェックを複製してはならず、正しいパラメーターリストで@Serviceを呼び出していることを確認してください。


グレーボックステストは少し落とし穴です。私はそれをDAOのようなものに制限する傾向があります。グレイボックステストが豊富で、ユニットテストがほぼ完全になく、ブラックボックステストが多すぎて、グレイボックステストでテストされていたものに対する信頼の欠如を補うことができなかったため、ビルドが非常に遅いいくつかのプロジェクトに参加しました。
Jilles van Gurp 2013年

これは、さまざまな状況でMockito.when()をいつ使用するかを答えるので、私にとってこれが最良の答えです。よくやった。
Michiel Leegwater

8

私はあなたが古典的なアプローチの観点から絶対的に正しいと言わなければなりません:

  • 最初にアプリケーションのビジネスロジック作成(または変更)し、次にそれを(採用)テストでカバーする場合Test-Lastアプローチ)、ソフトウェアの動作以外について、テストでソフトウェアの動作を通知することは非常に困難で危険です。入力と出力をチェックします。
  • テスト駆動アプローチを実践している場合、テストが最初に記述され、変更され、ソフトウェアの機能のユースケースを反映します実装はテストに依存します。つまり、ソフトウェアを特定の方法で実装したい場合があります。たとえば、他のコンポーネントのメソッドに依存したり、特定の回数だけ呼び出したりする場合もあります。Mockito.verify()役に立ちます。

普遍的なツールは存在しないことを覚えておくことが重要です。ソフトウェアの種類、サイズ、会社の目標と市場の状況、チームのスキル、その他多くのものが、特定のケースで使用するアプローチの決定に影響します。


0

一部の人々が言っ​​たように

  1. アサートできる直接出力がない場合があります
  2. 時には、テストしたメソッドが正しい間接出力をその共同作業者(あざける)に送信していることを確認する必要があるだけです。

リファクタリング時にテストを中断することについての懸念については、モック/スタブ/スパイを使用するときに多少予想されます。つまり、定義上、Mockitoなどの特定の実装に関するものではありません。しかし、このように考えることもできます。メソッドの動作方法に大きな変更を加えるリファクタリングを行う必要がある場合は、TDDアプローチで行うことをお勧めします。つまり、最初にテストを変更して、新しい動作(テストに失敗します)、次に変更を行い、テストに再度合格します。


0

Mockito.verifyの使用を好まないほとんどの場合、それはテスト対象のユニットが実行しているすべてのことを確認するために使用されるためです。しかし、それは問題ではないと思います。メソッドのテストを変更せずに、メソッドの動作を変更できるようにする場合、基本的には、メソッドの変更内容をテストしたくないため、メソッドが実行しているすべてをテストしないテストを作成することを意味します。 。そしてそれは間違った考え方です。

本当に問題なのは、メソッドの動作を変更でき、機能を完全にカバーするはずの単体テストが失敗しないことです。つまり、変更の意図が何であれ、変更​​の結果はテストの対象にはなりません。

そのため、私はできるだけモックすることを好みます。データオブジェクトもモックします。その際、verifyを使用して他のクラスの正しいメソッドが呼び出されていることを確認できるだけでなく、渡されるデータがそれらのデータオブジェクトの正しいメソッドを介して収集されていることも確認できます。そして、それを完全にするために、呼び出しが発生する順序をテストする必要があります。例:dbエンティティオブジェクトを変更し、それをリポジトリを使用して保存する場合、オブジェクトのセッターが正しいデータで呼び出され、リポジトリのsaveメソッドが呼び出されていることを確認するだけでは不十分です。それらが間違った順序で呼び出された場合でも、メソッドは実行すべきことを実行しません。したがって、私はMockito.verifyを使用しませんが、すべてのモックでinOrderオブジェクトを作成し、代わりにinOrder.verifyを使用します。また、完全にしたい場合は、Mockitoを呼び出す必要があります。最後にverifyNoMoreInteractionsを渡し、すべてのモックを渡します。そうしないと、誰かがテストせずに新しい機能/動作を追加する可能性があります。つまり、カバレッジ統計が100%になる可能性があるにもかかわらず、アサートまたは検証されていないコードを積み上げていることになります。

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