50

私はMockitoにかなり慣れていないので、クリーンアップに問題があります。

以前は単体テストに JMock2 を使用していました。私の知る限り、JMock2 は、テスト メソッドごとに再構築されるコンテキストで期待値やその他のモック情報を保持します。したがって、すべてのテスト方法が他の方法に干渉されることはありません。

私は JMock2 を使用するときにスプリング テストに同じ戦略を採用しました。投稿で使用した戦略に潜在的な問題があることがわかりました。アプリケーション コンテキストはテスト メソッドごとに再構築されるため、テスト手順全体が遅くなります。

多くの記事が春のテストで Mockito を使用することを推奨していることに気付きました。試してみたいと思います。テストケースに2つのテストメソッドを書くまではうまくいきます。各テスト メソッドは、単独で実行すると合格し、一緒に実行するとそのうちの 1 つが失敗しました。これは、モック情報がモック自体に保存されており (JMock にそのようなコンテキスト オブジェクトが表示されないため)、モック (およびアプリケーション コンテキスト) が両方のテスト メソッドで共有されているためだと推測しました。

@Before メソッドに reset() を追加して解決しました。私の質問は、この状況を処理するためのベストプラクティスは何ですか? どんなアイデアでも大歓迎です、事前に感謝します。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "file:src/main/webapp/WEB-INF/booking-servlet.xml",
    "classpath:test-booking-servlet.xml" })
@WebAppConfiguration
public class PlaceOrderControllerIntegrationTests implements IntegrationTests {

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Autowired
private PlaceOrderService placeOrderService;

@Before
public void setup() {
    this.mockMvc = webAppContextSetup(this.wac).build();

    reset(placeOrderService);// reset mock
}

@Test
public void fowardsToFoodSelectionViewAfterPendingOrderIsPlaced()
        throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenReturn(pendingOrder);

    mockMvc.perform(...);

}

@Test
public void returnsToPlaceOrderViewWhenFailsToPlaceOrder() throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    NoAvailableRestaurantException noAvailableRestaurantException = new NoAvailableRestaurantException(
            deliveryAddress, with(deliveryTime));
    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenThrow(noAvailableRestaurantException);

            mockMvc.perform(...);

}
4

6 に答える 6

62
  1. テストメソッドの後にリセットを配置することについて

    モックのリセットは、テスト中にクリーンアップする必要がある何かが実際に発生したことを意味するため、テストメソッドの後に行う方がよいと思います。

    テストメソッドの前にリセットが行われた場合、リセットする必要があるテストの前に一体何が起こったのかわからないでしょう。非モックオブジェクトはどうですか?それには理由がありますか(あるかもしれません)?コードで言及されていない理由がある場合 (メソッド名など)? など。

  2. Spring ベースのテストのファンではない

    1. バックグラウンド

      Spring を使用することは、クラスの単体テストをあきらめるようなものです。Spring を使用すると、テストの制御が少なくなります:分離インスタンス化ライフサイクル、単体テストで見えるプロパティのいくつかを引用します。ただし、多くの場合、Spring はそれほど「透過的」ではないライブラリとフレームワークを提供します。テストのために、Spring MVC、Spring Batch などのように、全体の実際の動作をより適切にテストします。

      また、これらのテストの作成は、多くの場合、開発者が本番コードの動作を真剣にテストするために統合テストを作成する必要があるため、はるかに面倒です。多くの開発者は、コードが Spring 内でどのように動作するかについてすべての詳細を知っているわけではないため、単体テストでクラスをテストしようとすると、多くの驚きにつながる可能性があります。

      しかし問題は続きます。開発者に迅速なフィードバックを提供するために、テストは高速かつ小規模である必要があります ( Infinitestなどの IDE プラグインはそのために最適です) が、Spring を使用したテストは本質的により遅く、より多くのメモリを消費します。これにより、実行頻度が低くなり、ローカルワークステーションで完全に回避される傾向があります...後でCIサーバーで失敗したことがわかります。

    2. Mockito と Spring によるライフサイクル

      そのため、サブシステム用に統合テストを作成すると、多くのオブジェクトと明らかに共同作業者が作成され、それらはおそらく嘲笑されます。ライフサイクルは Spring Runner によって制御されますが、Mockito モックは制御されません。そのため、モックのライフサイクルを自分で管理する必要があります。

      繰り返しますが、Spring Batch を使用したプロジェクト中のライフサイクルについて、非モックへの残留効果に関する問題がいくつかあったため、テスト クラスごとに 1 つのテスト メソッドのみを作成するか、ダーティ コンテキストトリックを使用するかの 2 つの選択肢がありました@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)。これにより、テストが遅くなり、メモリ消費量が増えますが、これが最善の選択肢でした。このトリックを使用すると、Mockito モックをリセットする必要がなくなります。

  3. 暗闇の中で可能な光

    私はこのプロジェクトについて十分に理解していませんが、springockitoはライフサイクルについていくらか教えてくれます。注釈サブプロジェクトはさらに優れているようです。Spring コンテナー内の Bean のライフサイクルを Spring に管理させ、モックの使用方法をテストに制御させるようです。まだ私はこのツールを使った経験がないので、驚きがあるかもしれません。

免責事項として、私は Spring が大好きです。Spring は他のフレームワークの使用を簡素化する多くの優れたツールを提供し、生産性を向上させ、設計に役立ちますが、人間が発明したすべてのツールと同様に、常に粗いエッジがあります (それ以上ではないにしても... )。

余談ですが、JUnitは各テスト メソッドのテスト クラスをインスタンス化するため、この問題がJUnitコンテキストで発生するのは興味深いことです。テストがTestNGに基づいている場合、TestNG はテスト クラスのインスタンスを 1 つしか作成しないため、アプローチは少し異なる可能性があります。Spring の使用に関係なく、モック フィールドの休止は必須です。


古い答え:

私は、春のコンテキストで Mockito モックを使用することの大ファンではありません。しかし、あなたは次のようなものを探しているでしょうか:

@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}
于 2013-08-12T09:58:26.520 に答える
21

Spring Boot には@MockBean、サービスのモックに使用できるアノテーションがあります。モックを手動でリセットする必要はもうありません。に置き換えるだけ@Autowiredです@MockBean

@MockBean
private PlaceOrderService placeOrderService;
于 2017-09-11T18:47:22.460 に答える
6

オブジェクトを注入する代わりに、次のような方法で、各テストの前にplaceOrderServiceMockito にオブジェクトを初期化させる必要があります。@Mock

@Mock private PlaceOrderService placeOrderService;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
}

ここの Javadoc で推奨されているとおり: http://docs.mockito.googlecode.com/hg/latest/org/mockito/MockitoAnnotations.html

@Beforeメソッドをスーパークラスに配置して、オブジェクトを使用するテスト ケース クラスごとに拡張することもでき@Mockます。

于 2013-08-10T20:33:47.880 に答える
5

Spring ベースのテストは、高速で独立したものにするのは困難です (@Briceが書いたように)。すべてのモックをリセットするための小さなユーティリティ メソッドを次に示します (すべての@Beforeメソッドで手動で呼び出す必要があります)。

import org.mockito.Mockito;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;


public class MyTest {
    public void resetAll(ApplicationContext applicationContext) throws Exception {
        for (String name : applicationContext.getBeanDefinitionNames()) {
            Object bean = applicationContext.getBean(name);
            if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
                bean = ((Advised)bean).getTargetSource().getTarget();
            }
            if (Mockito.mockingDetails(bean).isMock()) {
                Mockito.reset(bean);
            }
        }
    }
}

ご覧のように、すべての Bean の反復があるため、Bean がモックかどうかを確認し、モックをリセットします。通話AopUtils.isAopProxyや通話は特に気をつけています((Advised)bean).getTargetSource().getTarget()。Bean に注釈が含まれている場合@Transactional、この Bean のモックは常にスプリングによってプロキシ オブジェクトにラップされるため、このモックをリセットまたは検証するには、最初にラップを解除する必要があります。そうしないとUnfinishedVerificationException、さまざまなテストで時々発生する可能性のある a が得られます。

私の場合AopUtils.isAopProxyは十分です。しかし、プロキシで問題が発生した場合もAopUtils.isCglibProxyあります。AopUtils.isJdkDynamicProxy

mockitoは1.10.19 春のテストです3.2.2.RELEASE

于 2016-03-10T05:19:46.393 に答える
3

春のコンテキストでモックをリセットするさらに別の方法。

https://github.com/Eedanna/mockito/issues/119#issuecomment-166823815

Spring を使用していると仮定すると、ApplicationContext を取得して次の手順を実行することで、これを自分で簡単に実装できます。

public static void resetMocks(ApplicationContext context) {
    for ( String name : context.getBeanDefinitionNames() ) {
        Object bean = context.getBean( name );
        if (new MockUtil().isMock( bean )) {
            Mockito.reset( bean );
        }
    }
}

https://github.com/Eedanna/mockito/issues/119

上記のコードを @AfterAll と結合することは、モックをクリーンアップ/リセットする良い方法です。

于 2021-04-06T10:26:53.897 に答える