web-dev-qa-db-ja.com

これはMockitoのリセットメソッドの適切な使用ですか?

テストクラスに、一般的に使用される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
}
_

モックされたオブジェクトがsomeMethodを2回呼び出したため、これは失敗します。テストメソッドで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()の期待について知る必要があります。

77
Duncan Jones

これは、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には、コンストラクターでの呼び出しを検証するテストがあります。このテストでは、リセットにより、この単一のアクションとその影響を分離できます。これはreset()を使用した数少ないケースの1つとして役立ちます。それを使用しない他のオプションにはすべて短所があります。 OPがこの投稿を作成したという事実は、彼が単にリセットメソッドを盲目的に利用しているのではなく、状況について考えていることを示しています。

68
unholysampler

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

reset()の代わりに、長くて仕様が過剰なテストではなく、単純で小さく集中したテストメソッドを書くことを検討してください。最初の潜在的なコードのにおいは、テストメソッドの途中のreset()です。

mockito docs から抽出。

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

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

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

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

6
greuze

絶対違う。よくあることですが、クリーンなテストを作成する上での困難は、本番コードの設計に関する重大な問題です。この場合の最良の解決策は、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()を使用することをお勧めします。しかし、はっきりさせておきましょう。これは、コードの設計が不十分であることを示しています。

4
tonicsoft