JUnitで抽象クラスをテストする

JUnitを使用した抽象クラスのテスト

1. 概要

このチュートリアルでは、さまざまなユースケースと、非抽象メソッドを使用した抽象クラスの単体テストの可能な代替ソリューションを分析します。

testing abstract classes should almost always go through the public API of the concrete implementationsであるため、何をしているのか確信が持てない限り、以下の手法を適用しないでください。

2. Mavenの依存関係

Mavenの依存関係から始めましょう:


    org.junit.jupiter
    junit-jupiter-engine
    5.1.0
    test


    org.mockito
    mockito-core
    2.8.9
    test


    org.powermock
    powermock-module-junit4
    1.7.4
    test
    
        
            junit
            junit
        
    


    org.powermock
    powermock-api-mockito2
    1.7.4
    test

これらのライブラリの最新バージョンは、Maven Centralにあります。

PowermockはJunit5では完全にはサポートされていません。 また、powermock-module-junit4は、セクション5に示されている1つの例でのみ使用されます。

3. 独立した非抽象的方法

publicnon-abstractメソッドを持つ抽象クラスがある場合を考えてみましょう。

public abstract class AbstractIndependent {
    public abstract int abstractFunc();

    public String defaultImpl() {
        return "DEFAULT-1";
    }
}

メソッドdefaultImpl()をテストしたいのですが、具体的なクラスを使用するか、Mockitoを使用するかの2つの解決策が考えられます。

3.1. 具象クラスの使用

AbstractIndependent classを拡張する具象クラスを作成し、それを使用してメソッドをテストします。

public class ConcreteImpl extends AbstractIndependent {

    @Override
    public int abstractFunc() {
        return 4;
    }
}
@Test
public void givenNonAbstractMethod_whenConcreteImpl_testCorrectBehaviour() {
    ConcreteImpl conClass = new ConcreteImpl();
    String actual = conClass.defaultImpl();

    assertEquals("DEFAULT-1", actual);
}

このソリューションの欠点は、すべての抽象メソッドのダミー実装で具象クラスを作成する必要があることです。

3.2. Mockitoを使用する

または、Mockito を使用してモックを作成することもできます。

@Test
public void givenNonAbstractMethod_whenMockitoMock_testCorrectBehaviour() {
    AbstractIndependent absCls = Mockito.mock(
      AbstractIndependent.class,
      Mockito.CALLS_REAL_METHODS);

    assertEquals("DEFAULT-1", absCls.defaultImpl());
}

ここで最も重要な部分は、Mockito.CALLS_REAL_METHODSを使用するpreparation of the mock to use the real code when a method is invokedです。

4. 非抽象メソッドから呼び出される抽象メソッド

この場合、非抽象メソッドはグローバル実行フローを定義しますが、抽象メソッドはユースケースに応じてさまざまな方法で記述できます。

public abstract class AbstractMethodCalling {

    public abstract String abstractFunc();

    public String defaultImpl() {
        String res = abstractFunc();
        return (res == null) ? "Default" : (res + " Default");
    }
}

このコードをテストするには、以前と同じ2つのアプローチを使用できます。具体的なクラスを作成するか、orMockitoを使用してモックを作成します。

@Test
public void givenDefaultImpl_whenMockAbstractFunc_thenExpectedBehaviour() {
    AbstractMethodCalling cls = Mockito.mock(AbstractMethodCalling.class);
    Mockito.when(cls.abstractFunc())
      .thenReturn("Abstract");
    Mockito.doCallRealMethod()
      .when(cls)
      .defaultImpl();

    assertEquals("Abstract Default", cls.defaultImpl());
}

ここで、abstractFunc()は、テストに適した戻り値でスタブされています。 これは、非抽象メソッドdefaultImpl()を呼び出すときに、このスタブを使用することを意味します。

5. テスト障害のある非抽象的方法

いくつかのシナリオでは、テストするメソッドがテスト障害を含むプライベートメソッドを呼び出します。

ターゲットメソッドをテストする前に、妨害テストメソッドをバイパスする必要があります。

public abstract class AbstractPrivateMethods {

    public abstract int abstractFunc();

    public String defaultImpl() {
        return getCurrentDateTime() + "DEFAULT-1";
    }

    private String getCurrentDateTime() {
        return LocalDateTime.now().toString();
    }
}

この例では、defaultImpl()メソッドがプライベートメソッドgetCurrentDateTime()を呼び出します。 このプライベートメソッドは、実行時に現在の時刻を取得します。これは、ユニットテストでは回避する必要があります。

ここで、このプライベートメソッドの標準的な動作を模倣するために、プライベートメソッドを制御できないため、Mockitoを使用することもできません。

代わりに、PowerMocknote that this example works only with JUnit 4 because support for this dependency isn’t available for JUnit 5)を使用する必要があります。

@RunWith(PowerMockRunner.class)
@PrepareForTest(AbstractPrivateMethods.class)
public class AbstractPrivateMethodsUnitTest {

    @Test
    public void whenMockPrivateMethod_thenVerifyBehaviour() {
        AbstractPrivateMethods mockClass = PowerMockito.mock(AbstractPrivateMethods.class);
        PowerMockito.doCallRealMethod()
          .when(mockClass)
          .defaultImpl();
        String dateTime = LocalDateTime.now().toString();
        PowerMockito.doReturn(dateTime).when(mockClass, "getCurrentDateTime");
        String actual = mockClass.defaultImpl();

        assertEquals(dateTime + "DEFAULT-1", actual);
    }
}

この例の重要なビット:

  • @RunWith は、PowerMockをテストのランナーとして定義します

  • @PrepareForTest(class) は、PowerMockに、後の処理のためにクラスを準備するように指示します

興味深いことに、PowerMockにプライベートメソッドgetCurrentDateTime(). PowerMockをスタブするように依頼しています。これは、外部からアクセスできないため、リフレクションを使用して検索します。

したがって、を呼び出すと、実際のメソッドの代わりにプライベートメソッド用に作成されたスタブが呼び出されます。

6. インスタンスフィールドにアクセスする非抽象メソッド

抽象クラスは、クラスフィールドで実装された内部状態を持つことができます。 フィールドの値は、テストされるメソッドに大きな影響を与える可能性があります。

フィールドがパブリックまたは保護されている場合、テストメソッドから簡単にアクセスできます。

ただし、プライベートの場合は、PowerMockitoを使用する必要があります。

public abstract class AbstractInstanceFields {
    protected int count;
    private boolean active = false;

    public abstract int abstractFunc();

    public String testFunc() {
        if (count > 5) {
            return "Overflow";
        }
        return active ? "Added" : "Blocked";
    }
}

ここで、testFunc()メソッドは、返される前にインスタンスレベルのフィールドcountactive を使用しています。

testFunc()をテストする場合、Mockito. を使用して作成されたインスタンスにアクセスすることで、countフィールドの値を変更できます。

一方、プライベートactiveフィールドで動作をテストするには、PowerMockitoとそのWhiteboxクラスを再度使用する必要があります。

@Test
public void whenPowerMockitoAndActiveFieldTrue_thenCorrectBehaviour() {
    AbstractInstanceFields instClass = PowerMockito.mock(AbstractInstanceFields.class);
    PowerMockito.doCallRealMethod()
      .when(instClass)
      .testFunc();
    Whitebox.setInternalState(instClass, "active", true);

    assertEquals("Added", instClass.testFunc());
}

PowerMockito.mock()を使用してスタブクラスを作成し、Whiteboxクラスを使用してオブジェクトの内部状態を制御しています。

active フィールドの値がtrueに変更されます。

7. 結論

このチュートリアルでは、多くのユースケースをカバーする複数の例を見てきました。 従う設計に応じて、さらに多くのシナリオで抽象クラスを使用できます。

また、抽象クラスメソッドの単体テストを記述することは、通常のクラスおよびメソッドと同様に重要です。 さまざまな手法または利用可能なさまざまなテストサポートライブラリを使用して、それぞれをテストできます。

完全なソースコードはover on GitHubで入手できます。