1. 序章

テストクラスには、多くの場合、テスト対象のシステム、モック、またはテストで使用されるデータリソースを参照するメンバー変数が含まれています。 デフォルトでは、JUnit 4と5の両方が、各テストメソッドを実行する前にテストクラスの新しいインスタンスを作成します。これにより、テスト間の状態が明確に分離されます。

このチュートリアルでは、 JUnit 5 を使用して、@TestInstanceアノテーションを使用してテストクラスのライフサイクルを変更する方法を学習します。 また、これが大規模なリソースやテスト間のより複雑な関係の管理にどのように役立つかについても説明します。

2. デフォルトのテストライフサイクル

まず、JUnit4と5に共通するデフォルトのテストクラスのライフサイクルを見てみましょう。

class AdditionTest {

    private int sum = 1;

    @Test
    void addingTwoReturnsThree() {
        sum += 2;
        assertEquals(3, sum);
    }

    @Test
    void addingThreeReturnsFour() {
        sum += 3;
        assertEquals(4, sum);
    }
}

このコードは、JUnit5が必要としないpublic キーワードがないことを除けば、簡単にJUnit4または5のテストコードになる可能性があります。

各テストメソッドが呼び出される前にAdditionTestの新しいインスタンスが作成されるため、これらのテストは合格です。 これは、各テストの実行前に、変数sumの値が常に1に設定されることを意味します。

テストオブジェクトの共有インスタンスが1つしかない場合、変数sumはすべてのテスト後にその状態を保持します。 その結果、2番目のテストは失敗します。

3. @ BeforeClassおよび@BeforeAllアノテーション

複数のテストにまたがってオブジェクトが存在する必要がある場合があります。 テストデータとして使用するために大きなファイルを読み取りたいと想像してみましょう。 すべてのテストの前にそれを繰り返すのは時間がかかるかもしれないので、私たちはそれを一度読んで、テストフィクスチャ全体のためにそれを保持することを好むかもしれません。

JUnit 4は、@BeforeClassアノテーションでこれに対処します。

private static String largeContent;

@BeforeClass
public static void setUpFixture() {
    // read the file and store in 'largeContent'
}

JUnit4の@BeforeClassでアノテーションが付けられた変数とメソッドを静的にする必要があることに注意してください。

JUnit5は異なるアプローチを提供します。 クラスの静的メンバーを操作するために、静的関数で使用される@BeforeAllアノテーションを提供します。

ただし、テストインスタンスのライフサイクルがクラスごとに変更されている場合は、@BeforeAllをインスタンス関数およびインスタンスメンバーで使用することもできます。

4. @TestInstanceアノテーション

@TestInstance アノテーションを使用すると、JUnit5テストのライフサイクルを構成できます。

@TestInstanceには2つのモードがあります。1つはLifeCycle.PER_METHOD(デフォルト)です。 もう1つはLifeCycle.PER_CLASSです。 後者を使用すると、JUnitにテストクラスのインスタンスを1つだけ作成して、テスト間で再利用するように依頼できます。

テストクラスに@TestInstanceアノテーションを付けて、LifeCycle.PER_CLASSモードを使用してみましょう。

@TestInstance(LifeCycle.PER_CLASS)
class TweetSerializerUnitTest {

    private String largeContent;

    @BeforeAll
    void setUpFixture() {
        // read the file
    }

}

ご覧のとおり、変数や関数はいずれも静的ではありません。 PER_CLASS ライフサイクルを使用する場合、@BeforeAllにインスタンスメソッドを使用できます。

また、1つのテストによってインスタンス変数の状態に加えられた変更が、他のテストにも表示されるようになることにも注意してください。

5. @TestInstance(PER_CLASS)の使用

5.1. 高価なリソース

このアノテーションは、すべてのテストの前にクラスをインスタンス化するのに非常に費用がかかる場合に役立ちます。 例としては、データベース接続の確立や大きなファイルのロードなどがあります。

これを以前に解決すると、静的変数とインスタンス変数が複雑に混ざり合い、共有テストクラスインスタンスを使用することでよりクリーンになりました。

5.2. 意図的に状態を共有する

共有状態は通常、単体テストではアンチパターンですが、統合テストでは役立つ場合があります。クラスごとのライフサイクルは、意図的に状態を共有する順次テストをサポートします。 これは、特にテスト対象システムを適切な状態にするのが遅い場合に、後のテストで前のテストの手順を繰り返さなくて済むようにするために必要になる場合があります。

状態を共有する場合、すべてのテストを順番に実行するために、JUnit5はタイプレベルの@TestMethodOrderアノテーションを提供します。 次に、テストメソッドで @Order アノテーションを使用して、選択した順序でテストメソッドを実行できます。

@TestMethodOrder(OrderAnnotation.class)
class OrderUnitTest {

    @Test
    @Order(1)
    void firstTest() {
        // ...
    }

    @Test
    @Order(2)
    void secondTest() {
        // ...
    }

}

5.3. いくつかの状態を共有する

テストクラスの同じインスタンスを共有する場合の課題は、一部のメンバーをテスト間でクリーンアップする必要がある場合と、一部のメンバーをテスト全体の期間中維持する必要がある場合があることです。

@BeforeEachまたは@AfterEachで注釈が付けられたメソッドを使用して、テスト間でクリーンアップする必要のある変数をリセットできます。

6. 結論

このチュートリアルでは、 @TestInstance アノテーションと、それを使用してJUnit5テストのライフサイクルを構成する方法について学習しました。

また、共有リソースの処理や意図的な順次テストの作成という観点から、テストクラスの単一のインスタンスを共有することが役立つ理由についても説明しました。

いつものように、このチュートリアルのコードはGitHubにあります。