1. 序章

この記事では、 JUnit5を使用して並列単体テストを実行する方法について説明します。 最初に、この機能の使用を開始するための基本的な構成と最小要件について説明します。 次に、さまざまな状況のコード例を示し、最後に、共有リソースの同期について説明します。

並列テストの実行は、バージョン5.3以降のオプトインとして利用できる実験的な機能です。

2. 構成

まず、 src / test / resourcesフォルダーにjunit-platform.propertiesファイルを作成して、並列テスト実行を有効にする必要があります。 上記のファイルに次の行を追加することにより、並列化機能を有効にします。

junit.jupiter.execution.parallel.enabled = true

いくつかのテストを実行して、構成を確認しましょう。 まず、FirstParallelUnitTestクラスとその中に2つのテストを作成します。

public class FirstParallelUnitTest{

    @Test
    public void first() throws Exception{
        System.out.println("FirstParallelUnitTest first() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("FirstParallelUnitTest first() end => " + Thread.currentThread().getName());
    }

    @Test
    public void second() throws Exception{
        System.out.println("FirstParallelUnitTest second() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("FirstParallelUnitTest second() end => " + Thread.currentThread().getName());
    }
}

テストを実行すると、コンソールに次の出力が表示されます。

FirstParallelUnitTest second() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest second() end => ForkJoinPool-1-worker-19
FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19

この出力では、2つのことに気付くことができます。 まず、テストは順番に実行されます。 次に、 ForkJoin スレッドプールを使用します。並列実行を有効にすることで、JUnitエンジンはForkJoinスレッドプールの使用を開始します。

次に、このスレッドプールを利用するための構成を追加する必要があります。 並列化戦略を選択する必要があります。JUnitは、2つの実装(動的および固定)と、実装を作成するためのカスタムオプションを提供します。

動的戦略は、以下を使用して指定されたファクターパラメーター(デフォルトは1)を掛けたプロセッサー/コアの数に基づいてスレッドの数を決定します。

junit.jupiter.execution.parallel.config.dynamic.factor

一方、固定戦略は、次のように指定された事前定義されたスレッド数に依存します。

junit.jupiter.execution.parallel.config.fixed.parallelism

カスタム戦略を使用するには、最初にParallelExecutionConfigurationStrategyインターフェースを実装してカスタム戦略を作成する必要があります。

3. クラス内の並列化をテストする

すでに並列実行を有効にして、戦略を選択しました。 次に、同じクラス内でテストを並行して実行します。 これを構成するには2つの方法があります。 1つは@Execution(ExecutionMode.CONCURRENT)アノテーションを使用しており、もう1つはプロパティファイルと行を使用しています。

junit.jupiter.execution.parallel.mode.default = concurrent

これを構成する方法を選択し、 FirstParallelUnitTest クラスを実行すると、次の出力が表示されます。

FirstParallelUnitTest second() start => ForkJoinPool-1-worker-5
FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest second() end => ForkJoinPool-1-worker-5
FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19

出力から、両方のテストが同時に、2つの異なるスレッドで開始されていることがわかります。 出力は実行ごとに変わる可能性があることに注意してください。 これは、ForkJoinスレッドプールを使用する場合に予想されます。

同じスレッドのFirstParallelUnitTestクラス内ですべてのテストを実行するオプションもあります。 現在のスコープでは、並列処理と同じスレッドオプションを使用することは実行可能ではないため、スコープを拡張して、次のセクションでもう1つのテストクラスを追加しましょう。

4. モジュール内の並列化をテストする

新しいプロパティを導入する前に、 FirstParallelUnitTest:と同様の2つのメソッドを持つSecondParallelUnitTestクラスを作成します。

public class SecondParallelUnitTest{

    @Test
    public void first() throws Exception{
        System.out.println("SecondParallelUnitTest first() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("SecondParallelUnitTest first() end => " + Thread.currentThread().getName());
    }

    @Test
    public void second() throws Exception{
        System.out.println("SecondParallelUnitTest second() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("SecondParallelUnitTest second() end => " + Thread.currentThread().getName());
    }
}

同じバッチでテストを実行する前に、プロパティを設定する必要があります。

junit.jupiter.execution.parallel.mode.classes.default = concurrent

両方のテストクラスを実行すると、次の出力が得られます。

SecondParallelUnitTest second() start => ForkJoinPool-1-worker-23
FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest second() start => ForkJoinPool-1-worker-9
SecondParallelUnitTest first() start => ForkJoinPool-1-worker-5
FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19
SecondParallelUnitTest first() end => ForkJoinPool-1-worker-5
FirstParallelUnitTest second() end => ForkJoinPool-1-worker-9
SecondParallelUnitTest second() end => ForkJoinPool-1-worker-23

出力から、4つのテストすべてが異なるスレッドで並行して実行されていることがわかります。

このセクションと前のセクションで説明した2つのプロパティとそれらの値(same_threadとconcurrent)を組み合わせると、4つの異なる実行モードが得られます。

  1. same_thread、same_thread )–すべてのテストは順番に実行されます
  2. same_thread、concurrent )– 1つのクラスからのテストは順番に実行されますが、複数のクラスは並行して実行されます
  3. constant、same_thread )– 1つのクラスからのテストは並行して実行されますが、各クラスは別々に実行されます
  4. 同時、同時)–テストは並行して実行されます

5. 同期

理想的な状況では、すべての単体テストは独立しており、分離されています。 ただし、共有リソースに依存しているため、実装が難しい場合があります。 次に、テストを並行して実行する場合、テストで共通のリソースを同期する必要があります。 JUnit5は、@ResourceLockアノテーションの形式でそのようなメカニズムを提供します。

同様に、前と同じように、ParallelResourceLockUnitTestクラスを作成しましょう。

public class ParallelResourceLockUnitTest{
    private List<String> resources;
    @BeforeEach
    void before() {
        resources = new ArrayList<>();
        resources.add("test");
    }
    @AfterEach
    void after() {
        resources.clear();
    }
    @Test
    @ResourceLock(value = "resources")
    public void first() throws Exception {
        System.out.println("ParallelResourceLockUnitTest first() start => " + Thread.currentThread().getName());
        resources.add("first");
        System.out.println(resources);
        Thread.sleep(500);
        System.out.println("ParallelResourceLockUnitTest first() end => " + Thread.currentThread().getName());
    }
    @Test
    @ResourceLock(value = "resources")
    public void second() throws Exception {
        System.out.println("ParallelResourceLockUnitTest second() start => " + Thread.currentThread().getName());
        resources.add("second");
        System.out.println(resources);
        Thread.sleep(500);
        System.out.println("ParallelResourceLockUnitTest second() end => " + Thread.currentThread().getName());
    }
}

@ResourceLockを使用すると、共有するリソースと使用するロックのタイプを指定できます(デフォルトはResourceAccessMode.READ_WRITE)。 現在のセットアップでは、JUnitエンジンは、テストが共有リソースを使用していることを検出し、それらを順番に実行します。

ParallelResourceLockUnitTest second() start => ForkJoinPool-1-worker-5
[test, second]
ParallelResourceLockUnitTest second() end => ForkJoinPool-1-worker-5
ParallelResourceLockUnitTest first() start => ForkJoinPool-1-worker-19
[test, first]
ParallelResourceLockUnitTest first() end => ForkJoinPool-1-worker-19

6. 結論

この記事では、最初に、並列実行を構成する方法について説明しました。 次に、並列処理に利用できる戦略と、多数のスレッドを構成する方法を教えてください。 その後、さまざまな構成がテストの実行にどのように影響するかについて説明しました。 最後に、共有リソースの同期について説明しました。

いつものように、この記事のコードはGitHubにあります。