前言
junit5中引入了一种新的编程模型,就是动态测试,它是由TestFactory注解的工厂方法在运行时生成的
以下是本篇文章正文内容,下面案例可供参考
一、动态测试创建
前面学习的使用Test编写的用例是静态的测试用例
动态测试就是@DynamicTest注解声明方法,在运行的时候动态生成测试用例
- 与@Test相比 @DynamicTest本身不是测试用例,而是测试用例工厂用来生成测试用例的
- @DynamicTest与标准的@Test不同,执行的方式不同,像是BeforeEach注解执行生命周期不同,对于Test来说,每一个方法前都执行BeforeEach,但是对于DynamicTest来说是每一个工厂前执行一次,一个测试工厂里不只是有一个测试用例
3.Test方法是不能有返回值的,是void类型的方法,而DynamicTest是需要有返回值的
动态测试的构成:
1.方法必须用@TestFactory注解修饰
2.由显示名称和Executable组成
3.方法的返回值类型:steam Collection Iterable Iterator DynamicNode
下面创建静态测试用例和动态测试用例来看一下两者的区别和动态测试的用法,因为动态测试方法需要有返回值,这里使用的返回值为集合类型:
动态测试的用法:使用DynamicTest.dynamicTest 动态生成测试用例,其中传入2个参数 displayname 和 用例逻辑
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.DynamicTest;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 动态测试
*/
public class DynamicCaseTest {
@Test
@DisplayName("静态加法")
void add(){
assertEquals(5,3+2);
}
@Test
@DisplayName("静态除法")
void devide(){
assertEquals(5,25/5);
}
@TestFactory//返回集合
@DisplayName("动态测试")
Collection<DynamicNode> returnColl(){
DynamicTest addTest = DynamicTest.dynamicTest("动态加法", () -> {
assertEquals(5,3+2);
});
DynamicTest devideTest = DynamicTest.dynamicTest("动态除法", () -> {
assertEquals(5,25/5);
});
return Arrays.asList(addTest,devideTest);
}
}
上面代码执行结果如下,一个动态工厂可以产生多个测试用例,静态测试用例只是一个测试用例
得到结论:
1、动态测试是在运行的时候通过工厂使用@TestFactory注解生成的测试
2、@TestFactory注解不是声明测试用例,而是产生测试用例的工厂
二、动态测试规则
动态测试方法不能是private static 修饰
返回值必须是 Steam、Collection、Iterable 、Iterator、DynamicNode类型以及他们的实现类
DynamicNode是一个抽象类它有两个实现类DynamicTestDynamicContainer
下面举例来看一下各个返回值的用法:
- return DynamicTest
DynamicTest可以创建单个的测试用例
@TestFactory
@DisplayName("单个动态测试")
DynamicTest dynamicTest() {
return DynamicTest.dynamicTest("返回DynamicTest", () -> {
assertEquals(5, 2 + 3);
});
}
- return DynamicContainer
- DynamicContainer是测试用例容器,可以创建多个的测试用例
@TestFactory
@DisplayName("容器")
DynamicContainer dynamicContainer() {
DynamicTest test1 = DynamicTest.dynamicTest("加法", () -> {
assertEquals(5, 2 + 3);
});
DynamicTest test2 = DynamicTest.dynamicTest("除法", () -> {
assertEquals(5, 25/5);
});
//可以传多个测试数据,支持多种数据结构
return DynamicContainer.dynamicContainer("承载多个测试用例的容器", Stream.of(test1,test2));
}
- return Stream
单个Stream流的动态测试,如下是使用DynamicTest,显示参数化执行用例 :
@TestFactory
@DisplayName("动态测试单个_Stream")
Stream<DynamicTest> stream(){
//设置三个参数,将其作为参数 分别传入下面的测试用例中执行断言
Stream stream = Stream.of(6,7,8).map(
arg -> DynamicTest.dynamicTest("动态测试_"+arg, () -> {
System.out.println("参数:"+arg);
assertThat(arg,is(greaterThan(2)));
})
);
return stream;
}
执行结果如下:
多个Stream流的动态测试如下,是使用DynamicContainer,使用一个参数化,来实现多个动态测试
@TestFactory
@DisplayName("多个动态测试_Stream")
Stream<DynamicTest> streams() {
//设置三个参数,将其作为参数 分别传入下面的测试用例中执行断言
Stream stream = Stream.of(6, 7, 8).map(
arg -> DynamicContainer.dynamicContainer("动态测试_" + arg,
Stream.of(
DynamicTest.dynamicTest(arg + "大于5", () -> {
assertThat(arg,is(greaterThan(5)));
}),
DynamicTest.dynamicTest(arg + "小于10", () -> {
assertThat(arg, is(lessThan(10)));
})
))
);
return stream;
}
执行结果如下
- Collection
返回DynamicNode类型的集合,生成多个测试用例并执行
@TestFactory
@DisplayName("动态测试_collection")
Collection<DynamicTest> returnColl() {
DynamicTest addTest = DynamicTest.dynamicTest("动态加法", () -> {
assertEquals(5, 3 + 2);
});
DynamicTest devideTest = DynamicTest.dynamicTest("动态除法", () -> {
assertEquals(5, 25 / 5);
});
return Arrays.asList(addTest, devideTest);
}
- 迭代器-Iterator
创建一个迭代器,实现 从2开始加1,迭代3次
循环读取这个迭代器,动态生成测试用例,将测试用例放入集合中
最终将返回的是集合的迭代器(可迭代的对象)
@TestFactory
@DisplayName("迭代器1")
Iterator<DynamicTest> dynamicTestIterator() {
Collection<DynamicTest> dynamicTests = new ArrayList<>();
//创建一个迭代器 从 2 开始 加1 ,一共迭代3次
PrimitiveIterator.OfInt iterator =
IntStream.iterate(2, n -> n + 1).limit(3).iterator();
//执行迭代器,并将测试结果放入集合
while (iterator.hasNext()) {
Integer next = iterator.next();
DynamicTest test = DynamicTest.dynamicTest("n+1动态测试", () -> {
System.out.println("n:" + next);
assertThat(next, is(greaterThan(1)));
});
dynamicTests.add(test);
}
return dynamicTests.iterator();
}
- 迭代器-Iterable
Iterable与Iterator相似 ,Iterator返回值,是指返回可迭代对象,例如:集合.Iterator,并有hasNext() next()方法,而Iterable是直接返回可以迭代的元素例如 集合
@TestFactory
@DisplayName("迭代器2")
Iterable<DynamicTest> dynamicTestIterable() {
Collection<DynamicTest> dynamicTests = new ArrayList<>();
//创建一个迭代器 从 2 开始 加1 ,一共迭代3次
PrimitiveIterator.OfInt iterator =
IntStream.iterate(2, n -> n + 1).limit(3).iterator();
//执行迭代器,并将测试结果放入集合
while (iterator.hasNext()) {
Integer next = iterator.next();
DynamicTest test = DynamicTest.dynamicTest("n+1动态测试", () -> {
System.out.println("n:" + next);
assertThat(next, is(greaterThan(1)));
});
dynamicTests.add(test);
}
return dynamicTests;
}
如果动态测试方法返回值类型不是以上几种,怎会出现异常 JunitException
三、动态测试生命周期
表示声明周期常用注解 @BeforeAll
@BeforeEach @AfterAll @AfterEach
静态测试的生命周期运行:
在所有测试方法(@Test注解)运行前 先执行@BeforeAll 来进行资源或者参数的初始化等操作,在每一个测试方法执行前会执行一次@BeforeEach,用来执行测试用例的前置条件(公共方法),在每一个测试方法执行完后会执行@AfterEach,用来处理一些资源的销毁或者重置,最后在所有测试用例执行完的时候执行 @AfterAll,用来最后关闭资源等操作
动态测试的生命周期运行:
DynamicTest的方法只会执行一次BeforeEach和AfterEach,默认把BeforeEach和AfterEach的生命周期提升为了BeforeAll、AfterAll
在生成的动态测试用例角度看,不再是每一个用例前后执行一次,而是在DynamicTest动态测试工厂的运行前后执行
四、自定义动态测试执行顺序
动态测试的方法顺序不依赖于在顶级测试类上声明的@TestMethodOrder。
为了控制动态测试的顺序,我们可以使用自定义排序
这里的排序实际上是对返回值的内容进行排序,例如该动态工厂返回的是集合,需要自定义排序,就是对这个集合进行一些规则的排序,这里使用displayname进行的排序,下面的代码执行会倒序排序
public class DynamicOrderTest {
@TestFactory
//自定义用例执行顺序
List<DynamicTest> order1() {
List<DynamicTest> dynamicTests = null;
DynamicTest test1 = DynamicTest.dynamicTest("1stDy", () -> {
System.out.println("1stDy");
});
DynamicTest test2 = DynamicTest.dynamicTest("2ndDy", () -> {
System.out.println("2ndDy");
});
DynamicTest test3 = DynamicTest.dynamicTest("3rdDy", () -> {
System.out.println("3rdDy");
});
dynamicTests = Arrays.asList(test1, test2, test3);
sortDy(dynamicTests);
return dynamicTests;
}
//重写排序规则
static void sortDy(List<DynamicTest> dynamicTests) {
dynamicTests.sort(
(DynamicTest t1, DynamicTest t2) ->
t2.getDisplayName().compareTo(t1.getDisplayName()));//对displayname进行重排序
}
}
运行结果
五、动态测试参数化
常用Collection和Stream流遍历参数的形式实现参数化,如下所示
另外还可以结合配置文件形式将参数读入测试用例,这里是写死的
//参数化
public class DynamicParamTest {
@TestFactory
@DisplayName("集合参数化")
Collection<DynamicTest> dynamicTestCollection() {
Collection<DynamicTest> list = new ArrayList<>();
Arrays.asList("10.0.0.10", "127.0.0.1", "192.168.0.1").forEach(
s -> {
DynamicTest test1 = DynamicTest.dynamicTest("ip-test1:" + s, () -> {
System.out.println("ip:" + s);
});
DynamicTest test2 = DynamicTest.dynamicTest("ip-test2:" + s, () -> {
System.out.println("ip:-" + s);
});
list.add(test1);
list.add(test2);
}
);
return list;
}
@TestFactory
@DisplayName("Stream_参数化")
Stream<DynamicTest> streams() {
//设置三个参数,将其作为参数 分别传入下面的测试用例中执行断言
Stream stream = Stream.of(6, 7, 8).map(
arg -> DynamicContainer.dynamicContainer("动态测试_" + arg,
Stream.of(
DynamicTest.dynamicTest(arg + "大于5", () -> {
assertThat(arg, is(greaterThan(5)));
}),
DynamicTest.dynamicTest(arg + "小于10", () -> {
assertThat(arg, is(lessThan(10)));
})
))
);
return stream;
}
}
六、动态测试并发
动态测试并发,需要在配置文件junit-plantform.properties文件增加配置:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
使用@Execution(ExecutionMode.CONCURRENT)注解来表示该类并发执行
下面方法中有三个动态测试用例,方法会打印线程名称
/**
* 动态并发测试
*/
public class DynamicConTest {
@TestFactory
@Execution(ExecutionMode.CONCURRENT)
Collection<DynamicTest> concurrent() {
DynamicTest test1 = DynamicTest.dynamicTest("并发测试1", () -> {
System.out.println(Thread.currentThread().getName());
});
DynamicTest test2 = DynamicTest.dynamicTest("并发测试2", () -> {
System.out.println(Thread.currentThread().getName());
});
DynamicTest test3 = DynamicTest.dynamicTest("并发测试3", () -> {
System.out.println(Thread.currentThread().getName());
});
return Arrays.asList(test1,test2,test3);
}
}
运行结果如下,由此看出三个用例使用不同线程执行
注:@Execution(ExecutionMode.CONCURRENT)可以作用在类上也可以作用在方法上,作用在类上的话,整个类的方法都是并发执行,如果要想某个方法不进行并发执行,则在方法上注解:@Execution(ExecutionMode.SAME_THREAD)