1.介绍
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码
2.相关注解
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
- @ParameterizedTest:指定参数化测试
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参
3.极速初体验
代码
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class HelloTest {
@Order(1)
@DisplayName("多个字符串型入参")
@ParameterizedTest
@ValueSource(strings = { "a", "b", "c" })
void stringsTest(String candidate) {
log.info("stringsTest [{}]", candidate);
assertTrue(null!=candidate);
}
}
执行结果
4.版本要求
- JUnit5.6.2版本中,参数化测试还是体验版本
- JUnit5.7版本中,参数化测试才是稳定版本
如果是SpringBoot项目可以这么修改
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</exclusion>
</exclusions>
</dependency>
5.@ValueSource
介绍
ValueSource是最简单常用的数据源,支持基本数据类型与字符串
代码示例
@Order(2)
@DisplayName("多个int型入参")
@ParameterizedTest
@ValueSource(ints = { 1,2,3 })
void intsTest(int candidate) {
log.info("ints [{}]", candidate);
assertTrue(candidate<3);
}
运行结果
从上述代码可见,入参等于3的时候assertTrue无法通过,测试方法会失败,来看看实际执行效果,如下图:
6.@NullSource
介绍
在用字符串作为入参时,通常要考虑入参为null的情况
代码示例与运行结果
7.@EmptySource
介绍
与@NullSource代表null入参类似,@EmptySource代表空字符串入参
代码示例与运行结果
8.@NullAndEmptySource
介绍
如果想同时用null和空字符串做测试方法的入参,可以使用@NullAndEmptySource
代码示例与运行结果
9.@EnumSource
介绍
EnumSource可以让一个枚举类中的全部或者部分值作为测试方法的入参
代码示例
public enum Types {
SMALL,
BIG,
UNKNOWN
}
@Order(6)
@DisplayName("多个枚举型入参")
@ParameterizedTest
@EnumSource
void enumSourceTest(Types type) {
log.info("enumSourceTest [{}]", type);
}
运行结果
注意
如果不想执行枚举的所有值,而只要其中一部分,可以在name属性中指定
也可以指定哪些值不被执行,此时要添加mode属性并设置为EXCLUDE
@EnumSource(names={"SMALL", "UNKNOWN"})
10.@MethodSource
介绍
@MethodSource可以指定一个方法名称,该方法返回的元素集合作为测试方法的入参
代码示例
static Stream<String> stringProvider() {
return Stream.of("apple1", "banana1");
}
@Order(9)
@DisplayName("静态方法返回集合,用此集合中每个元素作为入参")
@ParameterizedTest
@MethodSource("stringProvider")
void methodSourceTest(String candidate) {
log.info("methodSourceTest [{}]", candidate);
}
@Order(10)
@DisplayName("静态方法返回集合,该静态方法在另一个类中")
@ParameterizedTest
@MethodSource("com.bolingcavalry.parameterized.service.impl.Utils#getStringStream")
void methodSourceFromOtherClassTest(String candidate) {
log.info("methodSourceFromOtherClassTest [{}]", candidate);
}
执行结果
注意
如果不在@MethodSource中指定方法名,JUnit会寻找和测试方法同名的静态方法,举例如下,静态方法methodSourceWithoutMethodNameTest会被作为测试方法的数据来源
11.@CsvSource
介绍
前面的测试方法入参都只有一个,在面对多个入参的测试方法时,@CsvSource就派上用场了,演示代码如下所示,可见数据是普通的CSV格式,每条记录有两个字段,对应测试方法的两个入参
代码示例
@Order(12)
@DisplayName("CSV格式多条记录入参")
@ParameterizedTest
@CsvSource({
"apple1, 11",
"banana1, 12",
"'lemon1, lime1', 0x0A"
})
void csvSourceTest(String fruit, int rank) {
log.info("csvSourceTest, fruit [{}], rank [{}]", fruit, rank);
}
运行结果
nullValues属性
@CsvSource还提供了一个属性nullValues,作用是将指定的字符串识别为null,下面这个设置就是把CSV数据中所有的NIL识别为null,再传给测试方法
@Order(13)
@DisplayName("CSV格式多条记录入参(识别null)")
@ParameterizedTest
@CsvSource(value = {
"apple2, 21",
"banana2, 22",
"'lemon2, lime2', 0x0A",
"NIL, 3" },
nullValues = "NIL"
)
void csvSourceWillNullTokenTest(String fruit, int rank) {
log.info("csvSourceWillNullTokenTest, fruit [{}], rank [{}]", fruit, rank);
}
12.@CsvFileSource
介绍
@CsvSource解决了测试方法入参有多个字段的问题,但是把作为入参的测试数据写在源文件中似乎不合适,尤其是数据量很大的情况下,这种场景适合用@CsvFileSource,该注解用于指定csv文件作为数据源,注意numLinesToSkip属性指定跳过的行数,可以用来跳过表头
代码示例
@Order(14)
@DisplayName("CSV文件多条记录入参")
@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void csvFileTest(String country, int reference) {
log.info("csvSourceTest, country [{}], reference [{}]", country, reference);
}
CSV文件内容
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3