EasyMockの紹介

EasyMockの概要

1. 前書き

これまで、JMockitMockitoについて幅広く話してきました。

このチュートリアルでは、別のモックツールであるEasyMockを紹介します。

2. Mavenの依存関係

飛び込む前に、次の依存関係をpom.xmlに追加しましょう。


    org.easymock
    easymock
    3.5.1
    test

最新バージョンは常にhereで見つけることができます。

3. コアコンセプト

モックを生成する場合、we can simulate the target object, specify its behavior, and finally verify whether it’s used as expected.

EasyMockのモックの操作には、次の4つのステップが含まれます。

  1. ターゲットクラスのモックを作成する

  2. アクション、結果、例外などを含む、予想される動作を記録する

  3. テストでモックを使用する

  4. 期待どおりに動作しているかどうかを確認する

記録が終了したら、「再生」モードに切り替えます。これにより、モックは、それを使用するオブジェクトとのコラボレーション時に記録されたとおりに動作します。

最終的に、すべてが期待どおりに進むかどうかを検証します。

上記の4つのステップは、org.easymock.EasyMockのメソッドに関連しています。

  1. mock(…):具象クラスであれインターフェースであれ、ターゲットクラスのモックを生成します。 作成されると、モックは「記録」モードになります。これは、EasyMockがモックオブジェクトが実行するアクションを記録し、「リプレイ」モードでリプレイすることを意味します。

  2. expect(…):このメソッドを使用すると、関連する記録アクションに対して、呼び出し、結果、例外などの期待値を設定できます。

  3. replay(…):指定されたモックを「再生」モードに切り替えます。 次に、以前に記録されたメソッド呼び出しをトリガーするアクションは、「記録された結果」を再生します

  4. verify(…):すべての期待が満たされ、モックで予期しない呼び出しが実行されなかったことを確認します**

次のセクションでは、実際の例を使用して、これらの手順が実際にどのように機能するかを示します。

4. モックの実例

先に進む前に、例のコンテキストを見てみましょう。例のブログの読者がWebサイトの記事を閲覧するのが好きで、その読者が記事を書き込もうとしているとします。

次のモデルを作成することから始めましょう。

public class exampleReader {

    private ArticleReader articleReader;
    private IArticleWriter articleWriter;

    // constructors

    public exampleArticle readNext(){
        return articleReader.next();
    }

    public List readTopic(String topic){
        return articleReader.ofTopic(topic);
    }

    public String write(String title, String content){
        return articleWriter.write(title, content);
    }
}

このモデルには、articleReader(具象クラス)とarticleWriter(インターフェース)の2つのプライベートメンバーがあります。

次に、それらをモックしてexampleReaderの動作を確認します。

5. Javaコードでモック

ArticleReaderをモックすることから始めましょう。

5.1. 典型的なモッキング

読者が記事をスキップすると、articleReader.next()メソッドが呼び出されることが期待されます。

@Test
public void whenReadNext_thenNextArticleRead(){
    ArticleReader mockArticleReader = mock(ArticleReader.class);
    exampleReader exampleReader
      = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();

    verify(mockArticleReader);
}

上記のサンプルコードでは、4ステップの手順に厳密に準拠し、ArticleReaderクラスをモックします。

expect(…).andReturn(…).を使用したAlthough we really don’t care what mockArticleReader.next() returns, we still need to specify a return value for mockArticleReader.next()

EasyMockは、expect(…)を使用すると、メソッドが値を返すか、Exception.をスローすることを期待しています。

単純に行う場合:

mockArticleReader.next();
replay(mockArticleReader);

EasyMockは、メソッドが何かを返す場合にexpect(…).andReturn(…)を呼び出す必要があるため、これについて文句を言います。

voidメソッドの場合、次のようにexpectLastCall()を使用してそのアクションをexpectできます。

mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);

5.2. リプレイオーダー

アクションを特定の順序で再生する必要がある場合は、より厳密にすることができます。

@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
    ArticleReader mockArticleReader
      = strictMock(ArticleReader.class);
    exampleReade exampleReader
      = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();
    exampleReader.readTopic("easymock");

    verify(mockArticleReader);
}

このスニペットでは、use strictMock(…) to check the order of method callsを使用しています。 mock(…)strictMock(…)によって作成されたモックの場合、予期しないメソッド呼び出しがあると、AssertionErrorが発生します。

モックのメソッド呼び出しを許可するには、niceMock(…)を使用できます。

@Test
public void whenReadNextAndOthers_thenAllowed(){
    ArticleReader mockArticleReader = niceMock(ArticleReader.class);
    exampleReade exampleReader = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();
    exampleReader.readTopic("easymock");

    verify(mockArticleReader);
}

ここでは、exampleReader.readTopic(…)が呼び出されるとは予想していませんでしたが、EasyMockは文句を言いません。 niceMock(…),を使用すると、EasyMockは、ターゲットオブジェクトが期待されるアクションを実行したかどうかのみを処理するようになりました。

5.3. Exceptionスローをあざける

それでは、インターフェースIArticleWriterのモックと、予想されるThrowablesの処理方法を続けましょう。

@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
    // mocking and initialization

    expect(mockArticleWriter
      .write("easymock",""))
      .andThrow(new IllegalArgumentException());
    replay(mockArticleWriter);

    // write malicious content and capture exception as expectedException

    verify(mockArticleWriter);
    assertEquals(
      IllegalArgumentException.class,
      expectedException.getClass());
}

上記のスニペットでは、articleWriterXSS(Cross-site Scripting)攻撃を検出するのに十分なほど堅固であると予想しています。

したがって、リーダーが悪意のあるコードを記事のコンテンツに挿入しようとすると、ライターはIllegalArgumentExceptionをスローする必要があります。 この予想される動作をexpect(…).andThrow(…)を使用して記録しました。

6. 注釈付きのモック

EasyMockは、注釈を使用したモックの挿入もサポートしています。 To use them, we need to run our unit tests with EasyMockRunner so that it processes @Mock and @TestSubject annotations.

以前のスニペットを書き直してみましょう。

@RunWith(EasyMockRunner.class)
public class exampleReaderAnnotatedTest {

    @Mock
    ArticleReader mockArticleReader;

    @TestSubject
    exampleReader exampleReader = new exampleReader();

    @Test
    public void whenReadNext_thenNextArticleRead() {
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        exampleReader.readNext();
        verify(mockArticleReader);
    }
}

mock(…)と同等で、モックは@Mockで注釈が付けられたフィールドに注入されます。 そして、これらのモックは、@TestSubjectで注釈が付けられたクラスのフィールドに注入されます。

上記のスニペットでは、exampleReader.articleReaderフィールドを明示的に初期化していません。exampleReader.readNext()を呼び出すときに、暗黙的に呼び出されたmockArticleReaderを挿入できます。

これは、mockArticleReaderthe articleReaderフィールドに挿入されたためです。

EasyMockRunnerの代わりに別のテストランナーを使用する場合は、JUnitテストルールEasyMockRuleを使用できることに注意してください。

public class exampleReaderAnnotatedWithRuleTest {

    @Rule
    public EasyMockRule mockRule = new EasyMockRule(this);

    //...

    @Test
    public void whenReadNext_thenNextArticleRead(){
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        exampleReader.readNext();
        verify(mockArticleReader);
    }

}

7. EasyMockSupportでモック

1つのテストで複数のモックを導入する必要がある場合があり、手動で繰り返す必要があります。

replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);

これは見苦しく、エレガントなソリューションが必要です。

幸い、これに対処するのに役立つクラスEasyMockSupport in EasyMockがあります。 これは、次のようなバッチをhelps keep track of mocks, such that we can replay and verify them inします。

//...
public class exampleReaderMockSupportTest extends EasyMockSupport{

    //...

    @Test
    public void whenReadAndWriteSequencially_thenWorks(){
        expect(mockArticleReader.next()).andReturn(null)
          .times(2).andThrow(new NoSuchElementException());
        expect(mockArticleWriter.write("title", "content"))
          .andReturn("BAEL-201801");
        replayAll();

        // execute read and write operations consecutively

        verifyAll();

        assertEquals(
          NoSuchElementException.class,
          expectedException.getClass());
        assertEquals("BAEL-201801", articleId);
    }

}

ここでは、articleReaderarticleWriterの両方をモックしました。 これらのモックを「再生」モードに設定するとき、EasyMockSupportによって提供される静的メソッドreplayAll()を使用し、verifyAll()を使用してバッチでの動作を検証しました。

また、expectフェーズでtimes(…)メソッドを導入しました。 重複コードの導入を回避できるように、メソッドが呼び出される回数を指定するのに役立ちます。

委任を通じてEasyMockSupportを使用することもできます。

EasyMockSupport easyMockSupport = new EasyMockSupport();

@Test
public void whenReadAndWriteSequencially_thenWorks(){
    ArticleReader mockArticleReader = easyMockSupport
      .createMock(ArticleReader.class);
    IArticleWriter mockArticleWriter = easyMockSupport
      .createMock(IArticleWriter.class);
    exampleReader exampleReader = new exampleReader(
      mockArticleReader, mockArticleWriter);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleWriter.write("title", "content"))
      .andReturn("");
    easyMockSupport.replayAll();

    exampleReader.readNext();
    exampleReader.write("title", "content");

    easyMockSupport.verifyAll();
}

以前は、静的メソッドまたは注釈を使用してモックを作成および管理していました。 内部的には、これらの静的モックと注釈付きモックは、グローバルEasyMockSupportインスタンスによって制御されます。

ここでは、明示的にインスタンス化して、委任を通じてこれらすべてのモックを独自の制御下に置きます。 これにより、EasyMockとのテストコードに名前の競合がある場合、または同様のケースがある場合に、混乱を避けることができます。

8. 結論

この記事では、EasyMockの基本的な使用法、モックオブジェクトの生成方法、動作の記録と再生、および正常に動作するかどうかの確認方法について簡単に紹介しました。

興味がある場合は、EasyMock、Mocket、およびJMockitの比較についてthis articleを確認してください。

いつものように、完全な実装はover on Githubで見つけることができます。