これはMockitoのリセット方法の適切な使用ですか?


68

テストクラスには、よく使用されるBarオブジェクトを作成するプライベートメソッドがあります。Barコンストラクタが呼び出されますsomeMethod()私の嘲笑オブジェクトのメソッドを:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

私がチェックしたいテストメソッドのいくつかでは、someMethodその特定のテストによっても呼び出されました。次のようなもの:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

モックされたオブジェクトがsomeMethod2回呼び出されたため、これは失敗します。私のテストメソッドが私のgetBar()メソッドの副作用を気にしたくないので、終了時にモックオブジェクトをリセットするのは合理的でしょうgetBar()か?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

ドキュメンテーションでモックオブジェクトのリセットは一般にテストの誤りを示していると示唆されているためお願いします。しかし、これは私には問題ないと感じています。

代替案

別の選択肢は呼び出しているようです:

verify(mockedObject, times(2)).someMethod();

私の意見では、各テストでの期待値を知るgetBar()必要があります。

回答:


60

これは、使用してreset()も大丈夫なケースの1つだと思います。あなたが書いているテストは、「何か」がの単一の呼び出しをトリガーすることをテストしていsomeMethod()ます。書き込みverify()呼び出しの任意の数が異なる文では、混乱を招くことができます。

  • atLeastOnce() テストが常に正しいことを望むので、これは悪いことです。
  • times(2)誤検知を防ぎますが、「コンストラクタが1つ追加することを知っている」と言うのではなく、2つの呼び出しを期待しているように見えます。さらに、コンストラクターで何かが変更されて余分な呼び出しが追加された場合、テストで誤検知が発生する可能性があります。また、テストが間違っているのではなく、テストが間違っているため、呼び出しを削除するとテストが失敗します。

reset()ヘルパーメソッドで使用することにより、これらの問題の両方を回避できます。ただし、行ったスタブをリセットすることにも注意する必要がありますので、注意してください。reset()推奨されない主な理由は、

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

これはOPがやろうとしていることではありません。OPには、コンストラクターでの呼び出しを検証するテストが含まれていると思われます。このテストでは、リセットにより、この単一のアクションとその効果を分離できます。この数少ないケースの1つは、次のような場合reset()に役立ちます。それを使用しない他のオプションにはすべて短所があります。OPがこの投稿をしたという事実は、彼が状況について考えているだけでなく、リセット方法を盲目的に利用しているだけではないことを示しています。


17
verify(...、times(...))の目的で過去の対話を忘れてスタブを保持するために、MockitoがresetInteractions()呼び出しを提供したいと思います。これにより、{setup;のテスト状況が作成されます。行為; 確認;}対処がはるかに簡単です。{セットアップ; resetInteractions; 行為; 確認}
Arkadiy

2
実際にはMockito 2.1以来、それはスタブをリセットせずに呼び出しをクリアする方法を提供しない:Mockito.clearInvocations(T... mocks)
コリン・D・ベネット

6

スマートMockitoユーザーは、テストが不十分な兆候である可能性があることを知っているため、リセット機能をほとんど使用しません。通常、モックをリセットする必要はありません。テストメソッドごとに新しいモックを作成するだけです。

代わりに、reset()長くて過剰に指定されたテストを対象に、シンプルで小さく焦点を絞ったテストメソッドを記述することを検討してください。最初の潜在的なコードの匂いはreset()、テスト方法の途中です。

mockito docsから抽出。

私のアドバイスは、の使用を避けようとすることreset()です。私の意見では、someMethodを2回呼び出す場合、テストする必要があります(データベースアクセス、または他の長い処理に注意する必要がある可能性があります)。

あなたが本当にそれを気にしないのであれば、あなたは使うことができます:

verify(mockedObject, atLeastOnce()).someMethod();

getBarからsomeMethodを呼び出した後ではなく、この最後の結果が誤った結果になる可能性があることに注意してください(これは間違った動作ですが、テストは失敗しません)。


2
はい、その正確な引用を見ました(質問からリンクしました)。現在、上記の例が「悪い」理由について、まともな議論はまだありません。供給できますか?
ダンカンジョーンズ

モックオブジェクトをリセットする必要がある場合は、テストで多くのものをテストしようとしているようです。それを2つのテストに分割して、小さなものをテストできます。いずれにせよ、getBarメソッド内で検証する理由がわかりません。テスト対象を追跡するのは困難です。クラスで何をすべきかでテスト思考を設計することをお勧めします(someMethodを正確に2回、少なくとも1回、1回だけ、決してしないなど)、すべての検証を同じ場所で呼び出す必要がある場合。
グルーズ

私の質問を編集してverify、プライベートメソッドを呼び出さなくても問題が解決しないことを強調しました(同意するでしょう、おそらくそこに属さない)。あなたの答えが変わるかどうかについてのコメントを歓迎します。
ダンカンジョーンズ

リセットを使用するのには多くの理由がありますが、この場合はそのmockitoの引用にあまり注意を払いません。特に、模擬データベース呼び出しやリフレクションの使用を気にしないプライベートメソッドを含む呼び出しを含むテストを実行している場合、テストスイートの実行時に不要な相互作用を引き起こす場合は、SpringのJUnitクラスランナーを使用できます。
サンディシモントン

私は通常、複数のことをテストしたいときにこれを難しいと感じますが、JUnitはテストをパラメーター化する良い(!)方法を提供していません。NUnitとは異なり、たとえば注釈付きです。
ステファンヘンドリックス

3

絶対違う。よくあることですが、クリーンなテストを書くのが難しいのは、実動コードの設計に関する重大な危険です。この場合、最善の解決策は、Barのコンストラクターがメソッドを呼び出さないようにコードをリファクタリングすることです。

コンストラクターは、ロジックを実行するのではなく、構築する必要があります。メソッドの戻り値を取得し、コンストラクターパラメーターとして渡します。

new Bar(mockedObject);

になる:

new Bar(mockedObject.someMethod());

これにより多くの場所でこのロジックが複製される場合、Barオブジェクトとは独立してテストできるファクトリメソッドを作成することを検討してください。

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

このリファクタリングが非常に難しい場合は、reset()を使用することで回避できます。しかし、明確にしましょう-これは、コードの設計が不十分であることを示しています。

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