1前書き

この記事では、JMockitの基本を超えて、次のような高度なシナリオについて見ていきます。

  • 偽造(または

    MockUp

    API)


  • Deencapsulation

    ユーティリティクラス

  • モックを1つだけ使って複数のインターフェースをモックする方法

  • 期待と検証を再利用する方法

JMockitの基本を知りたい場合は、このシリーズの他の記事をチェックしてください。あなたはページの下部に関連リンクを見つけることができます。

** 2プライベートメソッド/内部クラス

プライベートメソッドや内部クラスのモックやテストは、しばしば良い習慣とは見なされません。

その理由は、彼らが非公開の場合は、クラスの最も重要な部分であるため、直接テストするべきではないということですが、特にレガシーコードを扱う場合は、まだ実行する必要がある場合があります。

JMockitでは、これらを処理するための2つのオプションがあります。

実際の実装を変更するための

MockUp

API

場合)
** 任意のメソッドを直接呼び出すための

Deencapsulation

ユーティリティクラス

最初のケース)

以下のすべての例は、次のクラスに対して実行され、最初のクラスと同じ構成のテストクラスで実行されるとします(コードの繰り返しを避けるため)。

public class AdvancedCollaborator {
    int i;
    private int privateField = 5;

   //default constructor omitted

    public AdvancedCollaborator(String string) throws Exception{
        i = string.length();
    }

    public String methodThatCallsPrivateMethod(int i) {
        return privateMethod() + i;
    }
    public int methodThatReturnsThePrivateField() {
        return privateField;
    }
    private String privateMethod() {
        return "default:";
    }

    class InnerAdvancedCollaborator {...}
}


2.1.

MockUp


による偽造

JMockitのMockup APIは、偽の実装や「モックアップ」の作成をサポートします。通常、

mock-up

は、クラス内の少数のメソッドやコンストラクターを偽造することを目的としますが、他のほとんどのメソッドやコンストラクターは変更されないままにします。これにより、クラスの完全な書き換えが可能になるため、任意のメソッドまたはコンストラクタ(任意のアクセス修飾子付き)を対象にすることができます。

MockupのAPIを使用して

privateMethod()

を再定義する方法を見てみましょう。

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest {

    @Tested
    private AdvancedCollaborator mock;

    @Test
    public void testToMockUpPrivateMethod() {
        new MockUp<AdvancedCollaborator>() {
            @Mock
            private String privateMethod() {
                return "mocked: ";
            }
        };
        String res = mock.methodThatCallsPrivateMethod(1);
        assertEquals("mocked: 1", res);
    }
}

この例では、一致するシグネチャを持つメソッドで

@ Mock

アノテーションを使用して、

AdvancedCollaborator

クラスの新しい

MockUp

を定義しています。この後、そのメソッドへの呼び出しはモックされたものに委譲されます。

これを使って、テストを簡単にするために特定の引数や設定を必要とするクラスのコンストラクタをモックアップすることもできます。

@Test
public void testToMockUpDifficultConstructor() throws Exception{
    new MockUp<AdvancedCollaborator>() {
        @Mock
        public void $init(Invocation invocation, String string) {
            ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1;
        }
    };
    AdvancedCollaborator coll = new AdvancedCollaborator(null);
    assertEquals(1, coll.i);
}

この例では、コンストラクターモックのために

$ init

メソッドをモックする必要があることがわかります。

Invocation、

型の追加の引数を渡すことができます。これを使用して、呼び出しが実行されているインスタンスなど、モックメソッドの呼び出しに関する情報にアクセスできます。


2.2.

Deencapsulation

クラスを使用する

JMockitにはテストユーティリティクラス

Deencapsulation

が含まれています。その名前が示すように、オブジェクトの状態のカプセル化を解除するために使用され、それを使用すると、他の方法ではアクセスできないフィールドやメソッドにアクセスすることでテストを簡略化できます。

メソッドを呼び出すことができます。

@Test
public void testToCallPrivateMethodsDirectly(){
    Object value = Deencapsulation.invoke(mock, "privateMethod");
    assertEquals("default:", value);
}

フィールドを設定することもできます。

@Test
public void testToSetPrivateFieldDirectly(){
    Deencapsulation.setField(mock, "privateField", 10);
    assertEquals(10, mock.methodThatReturnsThePrivateField());
}

そして分野を得なさい:

@Test
public void testToGetPrivateFieldDirectly(){
    int value = Deencapsulation.getField(mock, "privateField");
    assertEquals(5, value);
}

そして、クラスの新しいインスタンスを作成します。

@Test
public void testToCreateNewInstanceDirectly(){
    AdvancedCollaborator coll = Deencapsulation
      .newInstance(AdvancedCollaborator.class, "foo");
    assertEquals(3, coll.i);
}

内部クラスの新しいインスタンスでさえも:

@Test
public void testToCreateNewInnerClassInstanceDirectly(){
    InnerCollaborator inner = Deencapsulation
      .newInnerInstance(InnerCollaborator.class, mock);
    assertNotNull(inner);
}

ご覧のとおり、

Deencapsulation

クラスは気密クラスをテストするときに非常に便利です。 1つの例は、プライベートフィールドに

@ Autowired

アノテーションを使用し、それらにセッターを持たないクラスの依存関係を設定すること、またはそのコンテナクラスのパブリックインターフェイスに依存することなく内部クラスをユニットテストすることです。


3同じモックで複数のインターフェースをモックする

まだ実装されていないクラスをテストしたいとしましょう。しかし、クラスがいくつかのインターフェースを実装することは確実です。

通常、このクラスを実装する前にテストすることはできませんが、JMockitを使用すると、1つのモックオブジェクトを使用して複数のインターフェイスをモックすることによってテストを事前に準備できます。

これは、総称を使用し、いくつかのインターフェースを拡張するタイプを定義することによって達成できます。このジェネリック型は、テストクラス全体または1つのテストメソッドに対してのみ定義できます。

たとえば、

List



Comparable

の2つの方法でモックを作成します____:

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest<MultiMock
  extends List<String> & Comparable<List<String>>> {

    @Mocked
    private MultiMock multiMock;

    @Test
    public void testOnClass() {
        new Expectations() {{
            multiMock.get(5); result = "foo";
            multiMock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", multiMock.get(5));
        assertEquals(0, multiMock.compareTo(new ArrayList<>()));
    }

    @Test
    public <M extends List<String> & Comparable<List<String>>>
      void testOnMethod(@Mocked M mock) {
        new Expectations() {{
            mock.get(5); result = "foo";
            mock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", mock.get(5));
        assertEquals(0, mock.compareTo(new ArrayList<>()));
    }
}

2行目からわかるように、クラス名に総称を使用して、テスト全体に対して新しいテストタイプを定義できます。そうすることで、

MultiMock

が型として利用可能になり、JMockitのアノテーションを使ってモックを作成することができます。

7から18行目で、テストクラス全体に対して定義されたマルチクラスのモックを使った例を見ることができます。

1回のテストでマルチインターフェイスモックが必要な場合は、メソッドシグネチャにジェネリック型を定義し、その新しいジェネリックの新しいモックをテストメソッド引数として渡すことでこれを実現できます。 20行目から32行目では、前のテストと同じテスト済みの動作に対して実行する例を見ることができます。


4期待と検証の再利用

結局、クラスをテストするとき、あなたは同じ

期待値や

検証を何度も繰り返している場合があるかもしれません。

それを簡単にするために、両方とも簡単に再利用できます。

例を挙げて説明します(リンク:/jmockit-101[JMockit 101]記事の

Modelクラス、Collaborator

クラス、および

Performer

クラスを使用しています)。

@RunWith(JMockit.class)
public class ReusingTest {

    @Injectable
    private Collaborator collaborator;

    @Mocked
    private Model model;

    @Tested
    private Performer performer;

    @Before
    public void setup(){
        new Expectations(){{
           model.getInfo(); result = "foo"; minTimes = 0;
           collaborator.collaborate("foo"); result = true; minTimes = 0;
        }};
    }

    @Test
    public void testWithSetup() {
        performer.perform(model);
        verifyTrueCalls(1);
    }

    protected void verifyTrueCalls(int calls){
        new Verifications(){{
           collaborator.receive(true); times = calls;
        }};
    }

    final class TrueCallsVerification extends Verifications{
        public TrueCallsVerification(int calls){
            collaborator.receive(true); times = calls;
        }
    }

    @Test
    public void testWithFinalClass() {
        performer.perform(model);
        new TrueCallsVerification(1);
    }
}

この例では、15から18行目で、

model.getInfo()

が常に

“ foo”

を返し、

collaborator.collaborate()

が常に

を期待するように、すべてのテストに対して期待値を準備していることがわかります。引数として“ foo”

を返し、

true

を返します。

minTimes = 0

ステートメントを配置したので、実際にテストでそれらを使用していなくても失敗は発生しません。

また、渡された引数が

true

の場合に

collaborator.receive(boolean)

メソッドへの検証を簡単にするために、

verifyTrueCalls(int)

メソッドを作成しました。

最後に、

Expectations

または

Verifications

クラスのいずれかを拡張しただけの新しいタイプの特定の期待および検証を作成することもできます。次に、33から43行目で行うように、動作を設定してテストでこの型の新しいインスタンスを作成する必要がある場合は、コンストラクタを定義します。


5結論

JMockitシリーズの今回の記事では、日常のモックとテストに間違いなく役立ついくつかの高度なトピックについて触れました。

私たちはJMockitについてもっと記事を書くかもしれないので、もっともっと学ぶようにしてください。

そして、いつものように、このチュートリアルの完全な実装はhttps://github.com/eugenp/tutorials/tree/master/testing-modules/mocks[GitHubにあります。]


5.1. シリーズの記事

シリーズのすべての記事:

  • リンク:/jmockit-101[JMockit 101]

  • リンク:/jmockit-expectations[JMockit Expectationsへのガイド]

  • リンク:/jmockit-advanced-usage[JMockitの高度なトピック]