我有超过2000个集成测试。 为了加快测试运行速度,我使用failsafe jvmfork并行运行我的测试。 我有一些比较耗时的测试类,它们通常在测试执行的最后运行,这会减慢我的CI验证过程。 文件安全的运行顺序:balanced对我来说是一个不错的选择,但由于jvmfork的原因我无法使用它。 重命名测试类或将其移动到另一个包并按字母顺序运行不是一个选项。 有没有建议如何在验证过程的开始运行我的耗时测试类?
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
org.junit.jupiter.api.ClassOrderer$ClassName
org.junit.jupiter.api.ClassOrderer$DisplayName
org.junit.jupiter.api.ClassOrderer$Random
除了junit-platform.properties文件之外,还有其他设置配置参数的方法,请参阅JUnit 5用户指南。
您还可以提供自己的排序器。它必须实现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
测试类不能按照 ClassOrderer
进行排序。从junit的5.8.0-M1版本开始,有一个解决方案可供使用。
基本上,您需要创建自己的排序器。我也做了这样的事情。
这是一个注释,您将在测试类中使用:
@Retention(RetentionPolicy.RUNTIME)
public @interface TestClassesOrder {
public int value() default Integer.MAX_VALUE;
}
那么,您需要创建一个类来实现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.jupiter.testclass.order.default=org.example.tests.AnnotationTestOrderer
现在你可以像使用Order注解一样,在类级别上使用你的订购者注解:
@TestClassesOrder(1)
class Tests {...}
@TestClassesOrder(2)
class MainTests {...}
@TestClassesOrder(3)
class EndToEndTests {...}
@TestClassesOrder
示例中语法有误。 - John John Pichler类库
的这些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
这基本上显示了LongRunningTest
与FastRunningTests
并行执行。并行执行的默认线程数由Concurrent
注释定义,默认值为5
,可以在FastRunningTests
的并行执行输出中看到。Threads
不共享FastRunningTest1
和FastRunningTest2
之间。TestSuites
(或编写自动生成它们的内容)
- 您需要为所有这些类定义并发注释(也许每个类的线程
数量都不同)。
由于这基本上表明可以定义类的执行顺序并触发它们的并行执行,所以应该也可能使整个过程只使用一个ThreadPool
(但我不确定这会有什么影响)。
由于整个概念是基于ThreadPoolExecutor构建的,使用PriorityBlockingQueue
将长时间运行的任务赋予更高的优先级,您将更接近执行长时间运行的测试的理想结果。
我做了更多实验,并实现了自己的自定义套件运行程序和JUnit运行程序。其背后的想法是让您的JUnitRunner将测试提交到由单个ThreadPoolExecutor
处理的队列中。由于我没有在RunnerScheduler#finish
方法中实现阻塞操作,因此在执行开始之前,所有类的测试都被传递到队列中。(如果涉及更多的测试类和方法,则情况可能会有所不同)。
至少证明了一点:如果您真的想要这样做,那么您可以在JUnit的这个级别上进行更改。
我的poc代码有点凌乱,太长了,放在这里不太合适,但如果有人感兴趣,我可以将它推送到GitHub项目中。
public interface SlowTestsCategory {}
@Category(SlowTestsCategory.class)
接下来,我们创建了一些特殊任务供Gradle按类别或自定义顺序运行测试:
task unitTest(type: Test) {
description = 'description.'
group = 'groupName'
useJUnit {
includeCategories 'package.SlowTestsCategory'
excludeCategories 'package.ExcludedCategory'
}
}
这个解决方案使用Gradle来提供支持,但它可能对您有所帮助。
build.gradle.kts:
sourceSets {
create("uiTest") {
// Adds files from the main source set to the compile classpath and runtime classpath of this new source set.
// sourceSets.main.output is a collection of all the directories containing compiled main classes and resources
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}
}
// Makes the uiTest configurations extend from test configurations,
// which means that all the declared dependencies of the test code (and transitively the main as well)
// also become dependencies of this new configuration
val uiTestImplementation by configurations.getting {
extendsFrom(configurations.testImplementation.get())
}
val uiTestRuntimeOnly by configurations.getting {
extendsFrom(configurations.testRuntimeOnly.get())
}
val uiTest = task<Test>("uiTest") {
description = "Runs UI tests."
group = "verification"
testClassesDirs = sourceSets["uiTest"].output.classesDirs
classpath = sourceSets["uiTest"].runtimeClasspath
mustRunAfter("test") // The important part
testLogging {
events(TestLogEvent.PASSED)
}
}
tasks.check { dependsOn(uiTest) }
tasks.withType<Test> {
useJUnitPlatform() // Enables JUnit 5 (along with JUnit 4) tests
}
src
--- main
--- java
--- resources
--- test
--- java
--- resources
--- uiTest
--- java
--- resources
在src/uiTest/java/...
目录中创建您的UI测试,就像在src/test/java/...
中一样。
现在,如果您执行check
Gradle任务(./gradlew check
)(这只是一个执行其他测试任务的任务),UI测试将在常规单元测试之后执行。
附注:
./gradlew uiTest
任务可以执行UI测试./gradlew test
任务可以执行单元测试在 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 可以相对容易地完成,帖子开头的链接中包含了您可能需要的所有信息。
JUnit5
对你来说是一个选择吗?根据此链接
,他们在那里添加了一些自定义排序功能。 - second