首先参考博客学习并行执行配置参数: Junit5的并行执行
有些场景下,单个测试类必须保证顺序性,在Junit5当中提供了两种方式:第一种是根据测试方法的字母先后顺序,另外一种是根据在测试上面添加注解来实现的。
对于字母的方式,比较简单,直接在测试类上面的添加如下的注解
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
对于通过注解的方式,需要测试类和测试方法同时添加注解TestMethodOrder
和Order
,前者指定使用的算法,后者指定执行的顺序(按照数字的小到大顺序执行),如下所示
package com.xquant.platform.test.asserts.client.acc;
import com.xquant.platform.test.asserts.client.AbstractExcelContextTests;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
/**
* Created in 2020/9/7 14:57 by guanglai.zhou
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CashAccountExtTest extends AbstractExcelContextTests {
/**
* 创建外部资金账户
*/
@Order(1)
@Test
public void testA_addCashAccExt() {
String resourceLocation = "classpath*:excel/acc/CashAccountExtTest.xlsx";
testSheetInExcel(resourceLocation, "addCashAccExt");
}
/**
* 修改外部资金账户
*/
@Order(2)
@Test
public void testB_updateCashAccExt() {
String resourceLocation = "classpath*:excel/acc/CashAccountExtTest.xlsx";
testSheetInExcel(resourceLocation, "updateCashAccExt");
}
/**
* 删除外部资金账户 依赖测试 test1AddCashAccExt 因此必须在test1AddCashAccExt之后
*/
@Order(3)
@Test
public void testC_deleteAccountExt() {
String resourceLocation = "classpath*:excel/acc/CashAccountExtTest.xlsx";
testSheetInExcel(resourceLocation, "deleteAccountExt");
}
}
当然用户可以自定义属于自己的算法。实现Junit5的接口org.junit.jupiter.api.MethodOrderer
并作为TestMethodOrder
注解的属性即可。由于以上两种方式能满足绝大部分要求,此处不再详述。
junit4中的@FixMethodOrder注解在junit5中测试方法的执行顺序没有任何作用
对应的源码如下所示:
org.junit.jupiter.engine.discovery.MethodOrderingVisitor
@Override
public void visit(TestDescriptor testDescriptor) {
if (testDescriptor instanceof ClassBasedTestDescriptor) {
ClassBasedTestDescriptor classBasedTestDescriptor = (ClassBasedTestDescriptor) testDescriptor;
try {
orderContainedMethods(classBasedTestDescriptor, classBasedTestDescriptor.getTestClass());
}
catch (Throwable t) {
BlacklistedExceptions.rethrowIfBlacklisted(t);
logger.error(t, () -> "Failed to order methods for " + classBasedTestDescriptor.getTestClass());
}
}
}
private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescriptor, Class<?> testClass) {
// 查找测试方法上面的注解 包括元注解 接口 父类中的注解 TestMethodOrder
findAnnotation(testClass, TestMethodOrder.class)
// 获取注解中的值 也就是MethodOrderer实现类
.map(TestMethodOrder::value)
// 通过反射创建实例 这个实现类必须包含默认的构造器
.map(ReflectionUtils::newInstance)
// 如果存在构造的对象
.ifPresent(methodOrderer -> {
// 根据测试类描述获取测试方法描述列表
Set<? extends TestDescriptor> children = classBasedTestDescriptor.getChildren();
// 获取没测试方法
List<TestDescriptor> nonMethodTestDescriptors = children.stream()
// 进行过滤 非测试方法返回true
.filter(testDescriptor -> !(testDescriptor instanceof MethodBasedTestDescriptor))
// 进行收集
.collect(Collectors.toList());
List<DefaultMethodDescriptor> methodDescriptors = children.stream()
// 过滤 类型判断
.filter(MethodBasedTestDescriptor.class::isInstance)
// 映射 类型强转
.map(MethodBasedTestDescriptor.class::cast)
// 映射 实例化类对象
.map(DefaultMethodDescriptor::new)
// 收集对象
.collect(toCollection(ArrayList::new));
// Make a local copy for later validation
Set<DefaultMethodDescriptor> originalMethodDescriptors = new LinkedHashSet<>(methodDescriptors);
// 进行排序
methodOrderer.orderMethods(
new DefaultMethodOrdererContext(methodDescriptors, testClass, this.configuration));
// 比较排序前后的方法数量
int difference = methodDescriptors.size() - originalMethodDescriptors.size();
if (difference > 0) {
logger.warn(() -> String.format(
"MethodOrderer [%s] added %s MethodDescriptor(s) for test class [%s] which will be ignored.",
methodOrderer.getClass().getName(), difference, testClass.getName()));
}
else if (difference < 0) {
logger.warn(() -> String.format(
"MethodOrderer [%s] removed %s MethodDescriptor(s) for test class [%s] which will be retained with arbitrary ordering.",
methodOrderer.getClass().getName(), -difference, testClass.getName()));
}
Set<TestDescriptor> sortedMethodTestDescriptors = methodDescriptors
// 获取流对象
.stream()
// 进行过滤 排序之前包含
.filter(originalMethodDescriptors::contains)
// 获取测试描述
.map(DefaultMethodDescriptor::getTestDescriptor)
// 收集到LinkedHashSet 因为需要排序
.collect(toCollection(LinkedHashSet::new));
// Currently no way to removeAll or addAll children at once.
// 通过以下操作保证了classBasedTestDescriptor中测试方法的顺序性
Stream.concat(sortedMethodTestDescriptors.stream(), nonMethodTestDescriptors.stream())//
.forEach(classBasedTestDescriptor::removeChild);
Stream.concat(sortedMethodTestDescriptors.stream(), nonMethodTestDescriptors.stream())//
.forEach(classBasedTestDescriptor::addChild);
// Note: MethodOrderer#getDefaultExecutionMode() is guaranteed
// to be invoked after MethodOrderer#orderMethods().
methodOrderer.getDefaultExecutionMode()
// 映射执行模式
.map(JupiterTestDescriptor::toExecutionMode)
// 设置默认方法执行模式
.ifPresent(classBasedTestDescriptor::setDefaultChildExecutionMode);
});
}
org.junit.jupiter.engine.descriptor.JupiterTestDescriptor#toExecutionMode
public static ExecutionMode toExecutionMode(org.junit.jupiter.api.parallel.ExecutionMode mode) {
switch (mode) {
case CONCURRENT:
return ExecutionMode.CONCURRENT;
case SAME_THREAD:
return ExecutionMode.SAME_THREAD;
}
throw new JUnitException("Unknown ExecutionMode: " + mode);
}
测试方法 protected final Set children = Collections.synchronizedSet(new LinkedHashSet<>(16));
对应的排序实现org.junit.jupiter.api.MethodOrderer.OrderAnnotation
/**
* {@code MethodOrderer} that sorts methods based on the {@link Order @Order}
* annotation. 根据@Order注解进行排序
*
* <p>Any methods that are assigned the same order value will be sorted
* arbitrarily adjacent to each other. 相同order值的方法任意顺序
*
* <p>Any methods not annotated with {@code @Order} will be assigned the
* {@link org.junit.jupiter.api.Order#DEFAULT default order} value which will
* effectively cause them to appear at the end of the sorted list, unless
* certain methods are assigned an explicit order value greater than the default
* order value. Any methods assigned an explicit order value greater than the
* default order value will appear after non-annotated methods in the sorted
* list. 没有包含注解的会赋默认值 Integer.MAX_VALUE / 2 不是0
*/
class OrderAnnotation implements MethodOrderer {
/**
* Sort the methods encapsulated in the supplied
* {@link MethodOrdererContext} based on the {@link Order @Order}
* annotation.
*/
@Override
public void orderMethods(MethodOrdererContext context) {
context.getMethodDescriptors()
// import static java.util.Comparator.comparingInt;
// java.util.Comparator#comparingInt(ToIntFunction<? super T> keyExtractor) 比较整数值
.sort(comparingInt(OrderAnnotation::getOrder));
}
private static int getOrder(MethodDescriptor descriptor) {
return descriptor.findAnnotation(Order.class)
// 获取Order值
.map(Order::value)
// 不包含注解则取默认值
.orElse(Order.DEFAULT);
}
}
通过以上的方式,我们定义了一个测试类内部的方法执行顺序。
在实际测试时,为了提高测试的速度,我们还需要设置测试并发执行。现在方法内已经设置了顺序执行,还能并发执行吗?可以的。
因为测试内部是顺序执行(同一个线程),而每个测试之间是可以并发执行的,没有顺序的要求,在文件junit-platform.properties
按照如下配置即可:
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent