junit测试类顺序

Sán*_*hos 10 java junit4 maven maven-failsafe-plugin

我有一个带有Maven的Java应用程序。Junit用于测试,带有故障安全和surefire插件。我有2000多个集成测试。为了加快测试的运行速度,我使用故障安全jvmfork并行运行测试。我有一些繁重的测试类,它们通常在测试执行结束时运行,这会减慢我的CI验证过程。filesafe runorder:balanced对我来说将是一个不错的选择,但由于jvmfork,我无法使用它。重命名测试类或移动到另一个软件包并按字母顺序运行它不是一种选择。有什么建议可以在验证过程开始时运行慢速测试类吗?

Mah*_*zad 7

JUnit 5(从5.8.0-M1版本开始)中,也可以订购测试类。

src/test/resources/ junit-platform.properties :

# ClassOrderer$OrderAnnotation sorts classes based on their @Order annotation
junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation
Run Code Online (Sandbox Code Playgroud)

其他 Junit 内置类排序器实现:

org.junit.jupiter.api.ClassOrderer$ClassName
org.junit.jupiter.api.ClassOrderer$DisplayName
org.junit.jupiter.api.ClassOrderer$Random
Run Code Online (Sandbox Code Playgroud)

有关设置配置参数的其他方式(除了junit-platform.properties文件),请参阅此处

您也可以提供自己的订购者。它必须实现ClassOrderer接口:

package foo;
public class MyOrderer implements ClassOrderer {
    @Override
    public void orderClasses(ClassOrdererContext context) {
        Collections.shuffle(context.getClassDescriptors());
    }
}
Run Code Online (Sandbox Code Playgroud)
junit.jupiter.testclass.order.default=foo.MyOrderer
Run Code Online (Sandbox Code Playgroud)

请注意,@Nested测试类不能按ClassOrderer.

请参阅 JUnit 5文档和 ClassOrderer api 文档以了解更多信息。


Mor*_*h21 6

从junit 5.8.0-M1版本开始有一个解决方案。

基本上你需要创建自己的排序者。我做了类似的事情。

这是您将在测试类中使用的注释:

@Retention(RetentionPolicy.RUNTIME)
public @interface TestClassesOrder {
    public int value() default Integer.MAX_VALUE;
}
Run Code Online (Sandbox Code Playgroud)

然后您需要创建将实现 org.junit.jupiter.api.ClassOrderer 的类

public class AnnotationTestsOrderer implements ClassOrderer {
    @Override
    public void orderClasses(ClassOrdererContext context) {
        Collections.sort(context.getClassDescriptors(), new Comparator<ClassDescriptor>() {
            @Override
            public int compare(ClassDescriptor o1, ClassDescriptor o2) {
                TestClassesOrder a1 = o1.getTestClass().getDeclaredAnnotation(TestClassesOrder.class);
                TestClassesOrder a2 = o2.getTestClass().getDeclaredAnnotation(TestClassesOrder.class);
                if (a1 == null) {
                    return 1;
                }

                if (a2 == null) {
                    return -1;
                }
                if (a1.value() < a2.value()) {
                    return -1;
                }

                if (a1.value() == a2.value()) {
                    return 0;
                }

                if (a1.value() > a2.value()) {
                    return 1;
                }
                return 0;
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

为了让它工作,你需要告诉 junit 你将使用哪个类来排序描述符。因此,您需要创建文件“ junit-platform.properties ”,它应该位于资源文件夹中。在该文件中,您只需要一行包含排序者类:

junit.jupiter.testclass.order.default=org.example.tests.AnnotationTestOrderer
Run Code Online (Sandbox Code Playgroud)

现在您可以像 Order 注释一样使用 orderer 注释,但在类级别:

@TestClassesOrder(1)
class Tests {...}

@TestClassesOrder(2)
class MainTests {...}

@TestClassesOrder(3)
class EndToEndTests {...}
Run Code Online (Sandbox Code Playgroud)

我希望这会对某人有所帮助。

  • 提到使用“junit-platform.properties”文件而不是普通的“application.properties”是正确的。 (2认同)

Jac*_*bec 5

在我们的项目中,我们创建了一些标记接口(例如

public interface SlowTestsCategory {}
Run Code Online (Sandbox Code Playgroud)

并将其放入测试速度慢的测试类中JUnit的@Category注释中。

@Category(SlowTestsCategory.class)
Run Code Online (Sandbox Code Playgroud)

之后,我们为 Gradle 创建了一些特殊任务,以按类别或按自定义顺序运行几个类别的测试:

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

  useJUnit {
    includeCategories 'package.SlowTestsCategory'
    excludeCategories 'package.ExcludedCategory'
  }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案由 Gradle 提供,但也许它会对您有所帮助。


sec*_*ond 5

我给出了尝试的答案组合:

第二个答案基于classesgithub项目的内容,可以通过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");
    }
}
Run Code Online (Sandbox Code Playgroud)
@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
}
Run Code Online (Sandbox Code Playgroud)

然后,我定义了测试套件:
(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 {}
Run Code Online (Sandbox Code Playgroud)

当我执行时,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

Which basically shows that the LongRunningTest is executed in parralel to the FastRunningTests. The default value of threads used for parallel execution defined by the Concurrent Annotation is 5, which can be seen in the output of the parallel execution of the FastRunningTests.

The downside is that theses Threads are not shared between FastRunningTest1 and FastRunningTest2.


This behavious shows that it is "somewhat" possible to do what you want to do (so whether that works with your current setup is a different question).

Also I am not sure whether this is actually worth the effort,

  • as you need to prepare those TestSuites manually (or write something that autogenerates them)
  • and you need to define the Concurrent Annotation for all those classes (maybe with a different number of threads for each class)

As this basically shows that it is possible to define the execution order of classes and trigger their parallel execution, it should also be possibly to get the whole process to only use one ThreadPool (but I am not sure what the implication of that would be).

As the whole concept is based on a ThreadPoolExecutor, using a PriorityBlockingQueue which gives long running tasks a higher priority you would get closer to your ideal outcome of executing the long running tests first.


I experimented around a bit more and implemented my own custom suite runner and junit runner. The idea behind is to have your JUnitRunner submit the tests into a queue which is handeld by a single ThreadPoolExecutor. Because I didn't implement a blocking operation in the RunnerScheduler#finish method, I ended up with a solution where the tests from all classes were passed to the queue before the execution even started. (That might look different if there a more test classes and methods involved).

At least it proves the point that you can mess with junit at this level if you really want to.

The code of my poc is a bit messy and to lengthy to put it here, but if someone is interested I can push it into a github project.


nic*_*tev 5

在我提供建议之前,让我总结一下。

  1. 集成测试很慢。这很好,而且很自然。
  2. CI 构建不运行假设系统部署的测试,因为 CI 中没有部署。我们关心 CD 过程中的部署。所以我假设您的集成测试不假设部署。
  3. CI 构建首先运行单元测试。单元测试非常快,因为它们只使用 RAM。
    我们从单元测试中获得了良好而快速的反馈。

目前,我们确信我们在获得快速反馈方面没有问题。但是我们仍然希望更快地运行集成测试。我会推荐以下解决方案:

  1. 改进实际测试。很多时候它们不是有效的,并且可以显着加速。
  2. 在后台运行集成测试(即不要等待他们的实时反馈)。
    它们比单元测试慢得多是很自然的。
  3. 如果您需要更快地从其中一些人那里获得反馈,则在组上拆分集成测试并单独运行它们。
  4. 在不同的 JVM 中运行集成测试。同一个 JVM 中不是不同的线程!
    在这种情况下,您不关心线程安全,也不应该关心它。
  5. 在不同的机器上运行集成测试等等。

我处理过许多不同的项目(其中一些项目的 CI 构建运行了 48 小时),前 3 个步骤就足够了(即使是疯狂的情况)。很少需要第 4 步进行良好的测试。第 5 步适用于非常特殊的情况。

您会看到我的建议与流程相关,而不是与工具相关,因为问题出在流程中。
人们经常忽略根本原因并尝试调整工具(在这种情况下为 Maven)。他们获得了外观上的改进,但创建的解决方案的维护成本很高。