参数化测试
JUnit通过@ParameterizedTest来实现参数化测试。
引入依赖
JUnit需要Java8以上的运行环境。
Maven
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
Gradle
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.1')
}
SHOW ME THE CODE
@ParameterizedTest
@ValueSource(strings = {"a", "b", "c"})
public void test(String s) {
assertNotNull(s);
}
从代码上来看,@ParameterizedTest申明方式与@Test大体上一致。不过使用@ParameterizedTest时,需要额外申明参数源。
参数源
@ValueSource
@ValueSource是最简单来源之一。它允许你指定一个基本类型的数组,并且它只能为每次调用提供一个参数。
@ParameterizedTest
@ValueSource(strings = {"a", "b", "c"})
public void testWithValueSource(String s) {
assertNotNull(s);
}
@EnumSource
@EnumSource能够很方便地提供Enum常量。该注解提供了一个可选的names参数,你可以用它来指定使用哪些常量。如果省略了,就意味着所有的常量将被使用,就像下面的例子所示。
@ParameterizedTest
@EnumSource(TimeUnit.class)
public void testWithEnumSource(TimeUnit timeUnit) {
assertNotNull(timeUnit);
}
@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = {"DAYS", "HOURS"})
public void testWithEnumSourceInclude(TimeUnit timeUnit) {
assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
}
@EnumSource注解还提供了一个可选的mode参数,它能够细粒度地控制哪些常量将会被传递到测试方法中。
排除模式
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = EnumSource.Mode.EXCLUDE, names = {"DAYS", "HOURS"})
public void testWithEnumSourceExclude(TimeUnit timeUnit) {
assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
}
正則匹配模式
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = EnumSource.Mode.MATCH_ALL, names = "^(M|N).+SECONDS$")
public void testWithEnumSourceRegex(TimeUnit timeUnit) {
String name = timeUnit.name();
assertTrue(name.startsWith("M") || name.startsWith("N"));
assertTrue(name.endsWith("SECONDS"));
}
@MethodSource
@MethodSource允许你引用测试类中的一个或多个工厂方法。这些工厂方法必须返回一个Stream、Iterable、Iterator或者参数数组。另外,它们不能接收任何参数。默认情况下,它们必须是static方法,除非测试类使用了@TestInstance(Lifecycle.PER_CLASS)注解。
@ParameterizedTest
@MethodSource("stringProvider")
void testWithSimpleMethodSource(String argument) {
assertNotNull(argument);
}
static List<String> stringProvider() {
return Arrays.asList("foo", "bar");
}
如果测试方法声明了多个参数,则需要返回一个Arguments实例的集合,如下面代码所示。
@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num,List<String> list) {
assertEquals(3, str.length());
assertTrue(num >=1 && num <=2);
assertEquals(2, list.size());
}
static Stream<Arguments> stringIntAndListProvider() {
return Stream.of(
Arguments.of("foo", 1, Arrays.asList("a", "b")),
Arguments.of("bar", 2, Arrays.asList("x", "y"))
);
}
@CsvSource
@CsvSource允许你将参数列表定义为以逗号分隔的值(即String类型的值)。一个空的引用值’'表示一个空的String;而一个完全空的值被当成一个null引用。如果null引用的目标类型是基本类型,则会抛出一个ArgumentConversionException。
@ParameterizedTest
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCsvSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
}
@CsvFileSource
@CsvFileSource允许你使用类路径中的CSV文件。CSV文件中的每一行都会触发参数化测试的一次调用。
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv")
void testWithCsvFileSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
}
two-column.csv
foo, 1
bar, 2
"baz, qux", 3
@ArgumentsSource
@ArgumentsSource 可以用来指定一个自定义且能够复用的ArgumentsProvider。
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
assertNotNull(argument);
}
static class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of("foo", "bar").map(Arguments::of);
}
}
参数类型转化
隐藏转化
为了支持像@CsvSource这样的使用场景,JUnit Jupiter提供了一些内置的隐式类型转换器。转换过程取决于每个方法参数的声明类型。
例如,如果一个@ParameterizedTest方法声明了TimeUnit类型的参数,而实际上提供了一个String,此时字符串会被自动转换成对应的TimeUnit枚举常量
@ParameterizedTest
@ValueSource(strings = "SECONDS")
void testWithImplicitArgumentConversion(TimeUnit argument) {
assertNotNull(argument.name());
}
具体转化列表可查看官方文档。
https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion
显示转换
除了使用隐式转换参数,你还可以使用@ConvertWith注解来显式指定一个ArgumentConverter用于某个参数。用户可以参照源码中的@JavaTimeConversionPattern实现自定义转换注解。
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithExplicitArgumentConversion(@ConvertWith(ToStringArgumentConverter.class) String argument) {
assertNotNull(TimeUnit.valueOf(argument));
}
static class ToStringArgumentConverter extends SimpleArgumentConverter {
@Override
protected Object convert(Object source, Class<?> targetType) {
assertEquals(String.class, targetType, "Can only convert to String");
return String.valueOf(source);
}
}