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
这基本上表明 LongRunningTest
与 FastRunningTests
并行执行。 Concurrent
注解定义的用于并行执行的线程默认值为5
,可以在FastRunningTests
.
注解的并行执行输出中看到
缺点是论文 Threads
不在 FastRunningTest1
和 FastRunningTest2
之间共享。
这种行为表明 "somewhat" 可以做您想做的事(因此这是否适用于您当前的设置是另一个问题)。
我也不确定这是否真的值得付出努力,
- 因为您需要手动准备这些
TestSuites
(或编写自动生成它们的东西)
- 并且您需要为所有这些 class 定义并发注释(每个 class 可能具有不同数量的
threads
)
这基本上表明可以定义classes的执行顺序并触发它们的并行执行,也应该可以让整个过程只使用一个ThreadPool
(但我不确定这意味着什么)。
由于整个概念基于 ThreadPoolExecutor,使用 PriorityBlockingQueue
为长 运行 任务提供更高的优先级,您将更接近执行长 运行 的理想结果]先测试。
我进行了更多试验并实现了我自己的自定义套件运行器和 junit 运行器。背后的想法是让您的 JUnitRunner 将测试提交到一个由单个 ThreadPoolExecutor
处理的队列中。因为我没有在 RunnerScheduler#finish
方法中实现阻塞操作,所以我最终得到了一个解决方案,在执行开始之前,所有 classes 的测试都被传递到队列中。 (如果涉及更多测试 classes 和方法,那看起来可能会有所不同)。
至少它证明了这一点,如果你真的想的话,你可以在这个级别上搞砸 junit。
我的 poc 的代码有点乱,放在这里太长了,但是如果有人感兴趣我可以把它推到一个 github 项目中。
让我总结一下,然后再提供建议。
- 集成测试很慢。这很好,很自然。
- CI build 不会 运行 测试假定系统已部署,因为 CI 中没有部署。我们关心 CD 过程中的部署。
所以我假设您的集成测试不假设部署。
- CI 首先构建 运行s 单元测试。单元测试非常快,因为它们只使用 RAM。
我们从单元测试中得到了良好而快速的反馈。
目前我们确信我们在获得快速反馈方面没有问题。但我们仍然希望 运行 集成测试更快。
我会推荐以下解决方案:
- 改进实际测试。很多时候它们并不有效,但可以显着加快速度。
- 运行 后台集成测试(即不要等待他们的实时反馈)。
他们自然比单元测试慢得多。
- 如果您需要更快地从他们中的某些人那里获得反馈,请将集成测试分开并 运行 单独进行。
- 运行 不同 JVM 中的集成测试。不是同一个 JVM 中的不同线程!
在这种情况下,您不关心线程安全,也不应该关心它。
- 运行 不同机器上的集成测试等等。
我参与过许多不同的项目(其中一些 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 {...}
我希望这会对某人有所帮助。
我有一个带有 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
这基本上表明 LongRunningTest
与 FastRunningTests
并行执行。 Concurrent
注解定义的用于并行执行的线程默认值为5
,可以在FastRunningTests
.
缺点是论文 Threads
不在 FastRunningTest1
和 FastRunningTest2
之间共享。
这种行为表明 "somewhat" 可以做您想做的事(因此这是否适用于您当前的设置是另一个问题)。
我也不确定这是否真的值得付出努力,
- 因为您需要手动准备这些
TestSuites
(或编写自动生成它们的东西) - 并且您需要为所有这些 class 定义并发注释(每个 class 可能具有不同数量的
threads
)
这基本上表明可以定义classes的执行顺序并触发它们的并行执行,也应该可以让整个过程只使用一个ThreadPool
(但我不确定这意味着什么)。
由于整个概念基于 ThreadPoolExecutor,使用 PriorityBlockingQueue
为长 运行 任务提供更高的优先级,您将更接近执行长 运行 的理想结果]先测试。
我进行了更多试验并实现了我自己的自定义套件运行器和 junit 运行器。背后的想法是让您的 JUnitRunner 将测试提交到一个由单个 ThreadPoolExecutor
处理的队列中。因为我没有在 RunnerScheduler#finish
方法中实现阻塞操作,所以我最终得到了一个解决方案,在执行开始之前,所有 classes 的测试都被传递到队列中。 (如果涉及更多测试 classes 和方法,那看起来可能会有所不同)。
至少它证明了这一点,如果你真的想的话,你可以在这个级别上搞砸 junit。
我的 poc 的代码有点乱,放在这里太长了,但是如果有人感兴趣我可以把它推到一个 github 项目中。
让我总结一下,然后再提供建议。
- 集成测试很慢。这很好,很自然。
- CI build 不会 运行 测试假定系统已部署,因为 CI 中没有部署。我们关心 CD 过程中的部署。 所以我假设您的集成测试不假设部署。
- CI 首先构建 运行s 单元测试。单元测试非常快,因为它们只使用 RAM。
我们从单元测试中得到了良好而快速的反馈。
目前我们确信我们在获得快速反馈方面没有问题。但我们仍然希望 运行 集成测试更快。 我会推荐以下解决方案:
- 改进实际测试。很多时候它们并不有效,但可以显着加快速度。
- 运行 后台集成测试(即不要等待他们的实时反馈)。
他们自然比单元测试慢得多。 - 如果您需要更快地从他们中的某些人那里获得反馈,请将集成测试分开并 运行 单独进行。
- 运行 不同 JVM 中的集成测试。不是同一个 JVM 中的不同线程!
在这种情况下,您不关心线程安全,也不应该关心它。 - 运行 不同机器上的集成测试等等。
我参与过许多不同的项目(其中一些 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 {...}
我希望这会对某人有所帮助。