前言
使用单元测试有时对方法的执行顺序有要求,而默认情况下测试方法的执行并非按照编写顺序,这就导致测试用例因执行顺序而导致的不通过。这里我使用的JUnit
版本是5.6.2,下面讲述如何自定义测试方法的执行优先级。
@TestMethodOrder
这个注解标注在测试类上,用于指定测试方法要以怎样的方式确定执行顺序:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TestMethodOrder {Class<? extends MethodOrderer> value();
}
value()
接收一个MethodOrderer
类型,这个类型是一个接口,里面重要的有一个接口方法orderMethods
以及三个静态实现类,分别是:Random
、OrderAnnotation
、Alphanumeric
:
public interface MethodOrderer {void orderMethods(MethodOrdererContext context);default Optional<ExecutionMode> getDefaultExecutionMode() {return Optional.of(ExecutionMode.SAME_THREAD);}// 随机顺序class Random implements MethodOrderer {// ...}// 根据@Order注解确定执行顺序,注意这个注解在org.junit.jupiter.api包下class OrderAnnotation implements MethodOrderer {// ...}// 按照方法名字母升序顺序,如果方法名相同,则拿方法的参数列表类型名称比较class Alphanumeric implements MethodOrderer {// ...}
}
其中比较重要的是void orderMethods(MethodOrdererContext context);
接口方法,如果你不满足于默认提供的三种实现,可以实现此方法,方法参数MethodOrdererContext
可以获得测试方法的上下文信息,据此定制方法执行顺序。一般来说上述提供的三种实现已基本满足需求,有需要可参照三种实现。
Random
随机确定方法执行顺序,只需要在测试类上标注如下:
@TestMethodOrder(MethodOrderer.Random.class)
class SampleTests {// methods ...
}
以下是Random
的关键源码:
class Random implements MethodOrderer {private static final long DEFAULT_SEED;static {DEFAULT_SEED = System.nanoTime(); // 默认的随机种子是系统时间,单位纳秒}public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; // 在配置文件中指定随机种子,这个值必须是数值型@Overridepublic void orderMethods(MethodOrdererContext context) {// 对方法随机洗牌Collections.shuffle(context.getMethodDescriptors(),new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED)));}// 获取配置文件中指定的随机种子private Optional<Long> getCustomSeed(MethodOrdererContext context) {return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> {Long seed = null;try {seed = Long.valueOf(configurationParameter);}catch (NumberFormatException ex) {}return seed;});}}
OrderAnnotation
如果需要为特定的方法指定执行顺序,需要在测试类上标注@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
,这个注解不能够单独使用,需要配合方法上的注解org.junit.jupiter.api.Order
确定执行顺序,你可以只为希望按顺序执行的方法标注此注解,而不是所有方法:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class SampleTests {@Test@Order(1) // 优先执行void shouldUploadFileSuccess() throws Exception {}@Test@Order(2) // 延后执行void shouldDeleteFileSuccess() throws Exception {}@Test // 不会因执行顺序而导致失败,可不指定@Ordervoid shouldListAllFiles() throws Exception {}
}
@Order
注解中的value
越小越优先执行,没有标注@Order
的方法使用的是DEFAULT
值:
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Order {int DEFAULT = Integer.MAX_VALUE / 2;int value();
}
下面是OrderAnnotation
实现:
class OrderAnnotation implements MethodOrderer {@Overridepublic void orderMethods(MethodOrdererContext context) {// java.util.Comparator.comparingIntcontext.getMethodDescriptors().sort(comparingInt(OrderAnnotation::getOrder));}// 获取方法上@Order注解的value,没有标注@Order注解的方法使用的是@Order注解上的DEFAULT值private static int getOrder(MethodDescriptor descriptor) {return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT);}}
方法排序表面使用的是Comparator
接口的静态方法comparingInt
,实际使用的是java.lang.Integer.compare
的比较逻辑:
public static int compare(int x, int y) {return (x < y) ? -1 : ((x == y) ? 0 : 1);}
Alphanumeric
按照方法名的字母升序排序执行,如果方法名相同,则拿方法的参数列表类型名称比较:
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
class AlphaTests {@Test // 后执行void b() {}@Test // 先执行void a() {}
}
下面是这种方式的实现:
class Alphanumeric implements MethodOrderer {@Overridepublic void orderMethods(MethodOrdererContext context) {context.getMethodDescriptors().sort(comparator);}private static final Comparator<MethodDescriptor> comparator = Comparator.<MethodDescriptor, String>// 拿方法名比较 comparing(descriptor -> descriptor.getMethod().getName())// 如果测试方法的名称相同,则进一步比较方法上的参数列表的类型.thenComparing(descriptor -> parameterList(descriptor.getMethod()));// 将方法的参数列表类型处理成形如:java.lang.String,java.lang.Integerdprivate static String parameterList(Method method) {return ClassUtils.nullSafeToString(method.getParameterTypes());}}
上面parameterList
方法用到的ClassUtils
是JUnit
自身的,实际并没什么可看,就是对给定的类型列表名称做逗号拼接处理(使用的是Java
8中的函数式编程,自行了解):
public final class ClassUtils {private ClassUtils() {}public static String nullSafeToString(Class<?> clazz) {return clazz == null ? "null" : clazz.getName();}// method.getParameterTypes()得到的是一个数组,所以使用的的是这个方法public static String nullSafeToString(Class<?>... classes) {// Class:getName => clazz -> clazz.getNamereturn nullSafeToString(Class::getName, classes);}// Function<? super Class<?>, ? extends String> mapper表示传入的是一个Class类型,// 将会产生一个String类型public static String nullSafeToString(Function<? super Class<?>, ? extends String> mapper, Class<?>... classes) {Preconditions.notNull(mapper, "Mapping function must not be null");return classes != null && classes.length != 0 ? (String)Arrays.stream(classes).map((clazz) -> {// mapper.apply(clazz)将调用nullSafeToString(Class::getName, classes)// 中的Class::getName用于获取类型限定名称return clazz == null ? "null" : (String)mapper.apply(clazz);}).collect(Collectors.joining(", ")) : "";}
}
软件版本
软件 | 版本 |
---|---|
JUnit | 5.6.2(junit-jupiter) |