web-dev-qa-db-ja.com

junitテストクラスの順序

Javaアプリ、テスト用のJunit、フェイルセーフおよびシュアファイアプラグインを使用しています。2000以上の統合テストがあります。テストの実行速度を上げるために、フェイルセーフjvmforkを使用してテストを実行しますパラレル。いくつかの重いテストクラスがあり、それらは通常、テスト実行の最後に実行され、CI検証プロセスが遅くなります。filesaferunorder:balancedは私にとっては良いオプションですが、jvmforkが原因で使用できません。テストクラスの名前を変更するか、別のパッケージに移動してそれを実行することはオプションではありません。検証プロセスの開始時に、遅いテストクラスを実行するにはどうすればよいですか?

10
Sándor Juhos

推奨事項を提供する前に、すべてを要約しましょう。

  1. 統合テストは遅いです。これは問題なく、自然なことです。
  2. CIにはデプロイメントがないため、CIビルドはシステムのデプロイメントを想定したテストを実行しません。 CDプロセスでの展開も気になります。したがって、統合テストはデプロイメントを想定していないと思います。
  3. CIビルドは最初に単体テストを実行します。単体テストはRAMのみを使用するため、非常に高速です。
    単体テストからの素早いフィードバックが得られます。

現時点では、迅速なフィードバックの取得に問題はないと確信しています。ただし、統合テストをより速く実行したいのです。私は次の解決策をお勧めします:

  1. 実際のテストを改善します。多くの場合、効果的ではなく、大幅に高速化できます。
  2. バックグラウンドで統合テストを実行します(つまり、それらからのリアルタイムのフィードバックを待ちません)。
    それらが単体テストよりもはるかに遅いのは自然なことです。
  3. グループの統合テストを分割し、一部のグループからのフィードバックがより速く必要な場合は、それらを個別に実行します。
  4. 異なるJVMで統合テストを実行します。同じJVM内の異なるスレッドではありません!
    この場合、スレッドの安全性は気にしないので、気にする必要はありません。
  5. 異なるマシンなどで統合テストを実行します。

私はさまざまなプロジェクト(CIビルドが48時間実行されていたものもある)を扱っており、最初の3ステップで十分です(クレイジーなケースでも)。手順4は、適切なテストを行う必要があることはめったにありません。ステップ#5は非常に特定の状況用です。

問題はプロセスにあるため、私の推奨事項はツールではなくプロセスに関連していることがわかります。
たいていの場合、根本原因を無視してツールを調整しようとします(この場合はMaven)。彼らは化粧品の改善を得ますが、作成されたソリューションの維持費は高くなります。

4
nickolay.laptev

私は試してみた答えの組み合わせを与えました:

2番目の答えは、これらの classes プロジェクトに基づいています github プロジェクトは、BSD-2ライセンスの下で利用できます。

私はいくつかのテストクラスを定義しました:

public class LongRunningTest {

    @Test
    public void test() {

        System.out.println(Thread.currentThread().getName() + ":\tlong test - started");

        long time = System.currentTimeMillis();
        do {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        } while(System.currentTimeMillis() - time < 1000);

        System.out.println(Thread.currentThread().getName() + ":\tlong test - done");
    }
}
@Concurrent
public class FastRunningTest1 {

    @Test
    public void test1() {
        try {
            Thread.sleep(250);
        } catch (InterruptedException e) {
        }

        System.out.println(Thread.currentThread().getName() + ":\tfrt1-test1 - done");
    }

    // +7 more repetions of the same method
}

次に、テストスイートを定義しました。
(FastRunningTest2は、出力が調整された最初のクラスのコピーです)

@SuiteClasses({LongRunningTest.class, LongRunningTest.class})
@RunWith(Suite.class)
public class SuiteOne {}

@SuiteClasses({FastRunningTest1.class, FastRunningTest2.class})
@RunWith(Suite.class)
public class SuiteTwo {}

@SuiteClasses({SuiteOne.class, SuiteTwo.class})
@RunWith(ConcurrentSuite.class)
public class TopLevelSuite {}

TopLevelSuiteを実行すると、次の出力が得られます。

TopLevelSuite-1-thread-1: long test - started FastRunningTest1-1-thread-4: frt1-test4 - done FastRunningTest1-1-thread-2: frt1-test2 - done FastRunningTest1-1-thread-1: frt1-test1 - done FastRunningTest1-1-thread-3: frt1-test3 - done FastRunningTest1-1-thread-5: frt1-test5 - done FastRunningTest1-1-thread-3: frt1-test6 - done FastRunningTest1-1-thread-1: frt1-test8 - done FastRunningTest1-1-thread-5: frt1-test7 - done FastRunningTest2-2-thread-1: frt2-test1 - done FastRunningTest2-2-thread-2: frt2-test2 - done FastRunningTest2-2-thread-5: frt2-test5 - done FastRunningTest2-2-thread-3: frt2-test3 - done FastRunningTest2-2-thread-4: frt2-test4 - done TopLevelSuite-1-thread-1: long test - done TopLevelSuite-1-thread-1: long test - started FastRunningTest2-2-thread-5: frt2-test8 - done FastRunningTest2-2-thread-2: frt2-test6 - done FastRunningTest2-2-thread-1: frt2-test7 - done TopLevelSuite-1-thread-1: long test - done

これは基本的に、LongRunningTestFastRunningTestsと並行して実行されることを示しています。 Concurrentアノテーションで定義された並列実行に使用されるスレッドのデフォルト値は5であり、FastRunningTestsの並列実行の出力で確認できます。

欠点は、これらのThreadsFastRunningTest1FastRunningTest2の間で共有されないことです。


この動作は、やりたいことを「ある程度」実行できることを示しています(そのため、現在の設定でそれが機能するかどうかは別の問題です)。

これが実際に努力する価値があるかどうかもわかりませんが、

  • TestSuitesを手動で準備する必要があるため(またはそれらを自動生成するものを記述する)
  • そして、あなたはそれらすべてのクラスのために同時注釈を定義する必要があります(おそらくクラスごとに異なる数のthreadsを使って)

これは基本的に、クラスの実行順序を定義して並列実行をトリガーできることを示しているため、プロセス全体で1つのThreadPoolのみを使用することも可能です(ただし、その意味はわかりません)そののだろう)。

全体の概念はThreadPoolExecutorに基づいているため、長期実行タスクに高い優先順位を与えるPriorityBlockingQueueを使用すると、長期実行テストを最初に実行する理想的な結果に近づきます。


もう少し実験して、独自のカスタムスイートランナーとjunitランナーを実装しました。背後にあるアイデアは、JUnitRunnerに、単一のThreadPoolExecutorによって処理されるキューにテストを送信させることです。 RunnerScheduler#finishメソッドにブロッキング操作を実装しなかったので、実行が開始される前にすべてのクラスのテストがキューに渡されるという解決策になりました。 (より多くのテストクラスとメソッドが含まれている場合、それは異なって見えるかもしれません).

少なくとも、本当に必要な場合は、このレベルでjunitをいじることができるということを証明します。

私のpocのコードは少し面倒でここに置くのに時間がかかりますが、誰かが興味があればそれをgithubプロジェクトにプッシュできます。

4
second

プロジェクトでは、いくつかのマーカーインターフェイス(例

public interface SlowTestsCategory {}

遅いテストのテストクラスで、JUnitの@Categoryアノテーションに挿入します。

@Category(SlowTestsCategory.class)

その後、Gradleがいくつかの特別なタスクを作成して、カテゴリ別またはいくつかのカテゴリ別にカスタムオーダーでテストを実行しました。

task unitTest(type: Test) {
  description = 'description.'
  group = 'groupName'

  useJUnit {
    includeCategories 'package.SlowTestsCategory'
    excludeCategories 'package.ExcludedCategory'
  }
}

このソリューションはGradleによって提供されますが、おそらく役立つでしょう。

4
Jackkobec

Junit 5のアノテーションを使用して、使用するテスト順序を設定できます。

Junit 5のユーザーガイドから: https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {

    @Test
    @Order(1)
    void nullValues() {
        // perform assertions against null values
    }

    @Test
    @Order(2)
    void emptyValues() {
        // perform assertions against empty values
    }

    @Test
    @Order(3)
    void validValues() {
        // perform assertions against valid values
    }

}

Junit5へのアップグレードはかなり簡単に行うことができ、投稿の冒頭にあるリンク上のドキュメントには、必要なすべての情報が含まれています。

0
agent_Karma