JUnit测试class顺序

JUnit test class order

我有一个带有 Maven 的 java 应用程序。 用于测试的 Junit,带有故障安全和万无一失的插件。 我有超过 2000 个集成测试。 为了加快测试 运行ning,我使用故障安全 jvmfork 来 运行 我的测试并行。 我有一些繁重的测试 class,它们通常 运行 在我的测试执行结束时出现,这会减慢我的 CI 验证过程。 filesafe runorder:balanced 对我来说是个不错的选择,但我不能使用它,因为 jvmfork. 要重命名测试 classes 或移动到另一个包并且 运行 它不是一个选项。 任何建议我如何 运行 我的慢测试 classes 在验证过程开始时?

在项目中我们创建了一些标记界面( 示例

public interface SlowTestsCategory {}

)

并在慢速测试class中放入JUnit的@Category注解

@Category(SlowTestsCategory.class)

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

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

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

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

我给了我找到的答案组合试试:

  • Running JUnit4 Test classes in specified order
  • Running JUnit Test in parallel on Suite Level

第二个答案基于这些 classes of this github 项目,该项目在 BSD-2 许可下可用。

我定义了几个测试classes:

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 是第一个 class 的副本,具有调整后的输出)

@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.

注解的并行执行输出中看到

缺点是论文 Threads 不在 FastRunningTest1FastRunningTest2 之间共享。


这种行为表明 "somewhat" 可以做您想做的事(因此这是否适用于您当前的设置是另一个问题)。

我也不确定这是否真的值得付出努力,

  • 因为您需要手动准备这些 TestSuites(或编写自动生成它们的东西)
  • 并且您需要为所有这些 class 定义并发注释(每个 class 可能具有不同数量的 threads

这基本上表明可以定义classes的执行顺序并触发它们的并行执行,也应该可以让整个过程只使用一个ThreadPool (但我不确定这意味着什么)。

由于整个概念基于 ThreadPoolExecutor,使用 PriorityBlockingQueue 为长 运行 任务提供更高的优先级,您将更接近执行长 运行 的理想结果]先测试。


我进行了更多试验并实现了我自己的自定义套件运行器和 junit 运行器。背后的想法是让您的 JUnitRunner 将测试提交到一个由单个 ThreadPoolExecutor 处理的队列中。因为我没有在 RunnerScheduler#finish 方法中实现阻塞操作,所以我最终得到了一个解决方案,在执行开始之前,所有 classes 的测试都被传递到队列中。 (如果涉及更多测试 classes 和方法,那看起来可能会有所不同)。

至少它证明了这一点,如果你真的想的话,你可以在这个级别上搞砸 junit。

我的 poc 的代码有点乱,放在这里太长了,但是如果有人感兴趣我可以把它推到一个 github 项目中。

让我总结一下,然后再提供建议。

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

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

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

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

你看我推荐的是流程,不是工具,因为问题出在流程上。
人们常常忽略根本原因并尝试调整工具(在本例中为 Maven)。他们获得了外观上的改进,但创建的解决方案的维护成本很高。

您可以在 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 相当容易,link 开头的 post 文档包含您可能需要的所有信息。

JUnit 5(从版本 5.8.0 开始)测试 classes 也可以订购。

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

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

org.junit.jupiter.api.ClassOrderer$ClassName
org.junit.jupiter.api.ClassOrderer$DisplayName
org.junit.jupiter.api.ClassOrderer$Random

其他方式(除junit-platform.properties文件外)设置配置参数见JUnit 5 user guide.

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

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

请注意 @Nested 测试 classes 不能由 ClassOrderer 排序。

请参阅 JUnit 5 documentations and ClassOrderer API docs 了解更多信息。

junit 的 5.8.0-M1 版本有一个解决方案。

基本上您需要创建自己的订购者。我做了类似的事情。

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

@Retention(RetentionPolicy.RUNTIME)
public @interface TestClassesOrder {
    public int value() default Integer.MAX_VALUE;
}

然后你需要创建 class 来实现 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;
            }
        });
    }
}

要让它工作,您需要告诉 junit 您将使用哪个 class 来排序描述符。所以你需要创建文件“junit-platform.properties”,它应该在资源文件夹中。在该文件中,您只需要一行与您的订购者 class:

junit.jupiter.testclass.order.default=org.example.tests.AnnotationTestOrderer

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

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

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

@TestClassesOrder(3)
class EndToEndTests {...}

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