在JUnit 4中,通过使用@Parameterized注释很容易在一堆类中测试不变量.关键是对一个参数列表运行一组测试.
如何在JUnit 5中复制它,而不使用JUnit-vintage?
@ParameterizedTest不适用于测试类.@TestTemplate听起来可能是合适的,但是注释的目标也是一种方法.
这种JUnit 4测试的一个例子是:
@RunWith( Parameterized.class )
public class FooInvariantsTest{
@Parameterized.Parameters
public static Collection<Object[]> data(){
return new Arrays.asList(
new Object[]{ new CsvFoo() ),
new Object[]{ new SqlFoo() ),
new Object[]{ new XmlFoo() ),
);
}
private Foo fooUnderTest;
public FooInvariantsTest( Foo fooToTest ){
fooUnderTest = fooToTest;
}
@Test
public void testInvariant1(){
...
}
@Test
public void testInvariant2(){
...
}
}
Run Code Online (Sandbox Code Playgroud)
dav*_*xxx 19
JUnit 5中的参数化测试功能不提供与JUnit 4提供的功能完全相同的功能.
引入了更多灵活性的新功能......但它也失去了JUnit4功能,其中参数化测试类使用参数化装置/断言在类级别,适用于该类的所有测试方法.需要通过指定"输入"来
定义@ParameterizedTest每个测试方法.
除此之外,我将介绍两个版本之间的主要区别以及如何在JUnit 5中使用参数化测试.
TL; DR
要编写一个参数化测试,指定一个值来逐个测试你的问题,你
org.junit.jupiter.params.provider.MethodSource应该做的工作.
@MethodSource允许您引用测试类的一个或多个方法.每个方法必须返回一个Stream,Iterable,Iterator,或的参数阵列.此外,每个方法都不能接受任何参数.默认情况下,除非使用注释测试类,否则此类方法必须是静态的@TestInstance(Lifecycle.PER_CLASS).如果只需要一个参数,则可以直接返回参数类型的实例,如以下示例所示.
作为JUnit 4,它@MethodSource依赖于工厂方法,也可以用于指定多个参数的测试方法.
在JUnit 5中,它是编写最接近JUnit 4的参数化测试的方法.
JUnit 4:
@Parameters
public static Collection<Object[]> data() {
Run Code Online (Sandbox Code Playgroud)
JUnit 5:
private static Stream<Arguments> data() {
Run Code Online (Sandbox Code Playgroud)
主要改进:
Collection<Object[]>变得Stream<Arguments>更加灵活.
将工厂方法绑定到测试方法的方式略有不同.
它现在更短,更不容易出错:不再需要创建构造函数并声明字段来设置每个参数的值.源的绑定直接在测试方法的参数上完成.
对于JUnit 4,在同一个类中,必须声明一个且只有一个工厂方法@Parameters.
使用JUnit 5,解除了这个限制:确实可以使用多种方法作为工厂方法.
因此,在类中,我们可以声明一些带有@MethodSource("..")引用不同工厂方法的测试方法.
例如,这是一个示例测试类,它声明了一些额外的计算:
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Assertions;
public class ParameterizedMethodSourceWithArgumentsTest {
@ParameterizedTest
@MethodSource("addFixture")
void add(int a, int b, int result) {
Assertions.assertEquals(result, a + b);
}
private static Stream<Arguments> addFixture() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(4, -4, 0),
Arguments.of(-3, -3, -6));
}
}
Run Code Online (Sandbox Code Playgroud)
要将现有的参数化测试从JUnit 4升级到JUnit 5,@MethodSource需要考虑.
总结
@MethodSource有一些优点,但也有一些弱点.
在JUnit 5中引入了指定参数化测试源的新方法.
这里有一些关于它们的附加信息(非常详尽),我希望能够以一般方式对如何处理提供一个广泛的想法.
介绍
JUnit 5 在这些术语中引入了参数化测试功能:
参数化测试可以使用不同的参数多次运行测试.它们被声明为常规
@Test方法,但使用@ParameterizedTest注释.此外,您必须至少声明一个将为每次调用提供参数的源.
依赖性要求
参数化测试功能不包含在junit-jupiter-engine核心依赖项中.
您应该添加一个特定的依赖项来使用它:junit-jupiter-params.
如果你使用Maven,这是声明的依赖:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
Run Code Online (Sandbox Code Playgroud)
可用于创建数据的来源
与JUnit 4相反,JUnit 5提供了多种风格和工件来编写参数化测试
.支持的方式通常取决于您要使用的数据源.
以下是框架提出并在文档中描述的源类型:
@ValueSource @EnumSource@MethodSource@CsvSource@CsvFileSource@ArgumentsSource以下是我实际使用JUnit 5的3个主要来源,我将介绍:
@MethodSource@ValueSource@CsvSource在我编写参数化测试时,我认为它们是基本的.他们应该允许在JUnit 5中编写,这是您描述的JUnit 4测试的类型.
@EnumSource,@ArgumentsSource并且@CsvFileSource 当然可以是有帮助的,但他们更专业.
演示@MethodSource,@ValueSource和@CsvSource
1) @MethodSource
此类源需要定义工厂方法.
但它也提供了很大的灵活性.
在JUnit 5中,它是编写最接近JUnit 4的参数化测试的方法.
如果测试方法中有一个方法参数,并且您希望使用任何类型作为源,那么这 @MethodSource是一个非常好的候选者.
要实现它,请定义一个方法,该方法返回每个案例的值的Stream,并使用@MethodSource("methodName")where methodName数据源方法的名称来注释测试方法.
例如,你可以写:
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class ParameterizedMethodSourceTest {
@ParameterizedTest
@MethodSource("getValue_is_never_null_fixture")
void getValue_is_never_null(Foo foo) {
Assertions.assertNotNull(foo.getValue());
}
private static Stream<Foo> getValue_is_never_null_fixture() {
return Stream.of(new CsvFoo(), new SqlFoo(), new XmlFoo());
}
}
Run Code Online (Sandbox Code Playgroud)
如果测试方法中有多个方法参数,并且您希望使用任何类型作为源,那么 @MethodSource也是一个非常好的候选者.
要实现它,请定义一个方法,org.junit.jupiter.params.provider.Arguments为每个案例返回一个Stream来进行测试.
例如,你可以写:
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Assertions;
public class ParameterizedMethodSourceWithArgumentsTest {
@ParameterizedTest
@MethodSource("getFormatFixture")
void getFormat(Foo foo, String extension) {
Assertions.assertEquals(extension, foo.getExtension());
}
private static Stream<Arguments> getFormatFixture() {
return Stream.of(
Arguments.of(new SqlFoo(), ".sql"),
Arguments.of(new CsvFoo(), ".csv"),
Arguments.of(new XmlFoo(), ".xml"));
}
}
Run Code Online (Sandbox Code Playgroud)
2)@ValueSource
如果在测试方法中有一个方法参数,并且您可以从这些内置类型之一(String,int,long,double)表示参数的来源, @ValueSource则为suit.
@ValueSource 确实定义了这些属性:
String[] strings() default {};
int[] ints() default {};
long[] longs() default {};
double[] doubles() default {};
Run Code Online (Sandbox Code Playgroud)
例如,你可以这样使用它:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class ParameterizedValueSourceTest {
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void sillyTestWithValueSource(int argument) {
Assertions.assertNotNull(argument);
}
}
Run Code Online (Sandbox Code Playgroud)
注意1)您不能指定多个注释属性.
注意2)方法的源和参数之间的映射可以在两种不同的类型之间完成.由于其解析,用作数据源
的类型String特别允许转换为多种其他类型.
3) @CsvSource
如果测试方法中有 多个方法参数,则@CsvSource可能适合.
要使用它,请使用@CsvSource并在String每个案例的数组中指定测试.
每个案例的值用逗号分隔.
类似地@ValueSource,方法的源和参数之间的映射可以在两种不同类型之间完成.
这是一个例子,说明:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class ParameterizedCsvSourceTest {
@ParameterizedTest
@CsvSource({ "12,3,4", "12,2,6" })
public void divideTest(int n, int d, int q) {
Assertions.assertEquals(q, n / d);
}
}
Run Code Online (Sandbox Code Playgroud)
@CsvSource VS @MethodSource
这些源类型提供了非常经典的要求:从源映射到测试方法中的多个方法参数.
但他们的方法不同.
@CsvSource有一些优点:它更清晰,更短.
实际上,参数定义在测试方法的正上方,无需创建可能另外生成"未使用"警告的夹具方法.
但它也有关于映射类型的重要限制.
你必须提供一个数组String.该框架提供转换功能,但它是有限的.
总而言之,虽然String提供的源代码和测试方法的参数具有相同的类型(String- > String)或依赖于内置转换(例如String- > int),但@CsvSource显示为使用方式.
由于情况并非如此,您必须在保持灵活性之间做出选择,即为框架未执行的转换@CsvSource创建自定义转换器(ArgumentConverter子类)或使用@MethodSource返回的工厂方法Stream<Arguments>.
它具有上述缺点,但它也具有从源到参数的任何类型的开箱即用映射的巨大好处.
参数转换
关于源(@CsvSource或@ValueSource例如)与测试方法的参数之间的映射,如图所示,如果类型不同,框架允许进行一些转换.
以下是两种转换的演示:
3.13.3.参数转换
隐式转换
为了支持这样的用例
@CsvSource,JUnit Jupiter提供了许多内置的隐式类型转换器.转换过程取决于每个方法参数的声明类型......
String实例当前隐式转换为以下目标类型.Run Code Online (Sandbox Code Playgroud)Target Type | Example boolean/Boolean | "true" ? true byte/Byte | "1" ? (byte) 1 char/Character | "o" ? 'o' short/Short | "1" ? (short) 1 int/Integer | "1" ? 1 .....
例如,在前面的示例中,隐式转换是在Stringsource 之间完成的,并且int定义为参数:
@CsvSource({ "12,3,4", "12,2,6" })
public void divideTest(int n, int d, int q) {
Assertions.assertEquals(q, n / d);
}
Run Code Online (Sandbox Code Playgroud)
在这里,从String源到LocalDate参数进行隐式转换:
@ParameterizedTest
@ValueSource(strings = { "2018-01-01", "2018-02-01", "2018-03-01" })
void testWithValueSource(LocalDate date) {
Assertions.assertTrue(date.getYear() == 2018);
}
Run Code Online (Sandbox Code Playgroud)
如果对于两种类型,框架不提供转换(自定义类型的情况),则应使用ArgumentConverter.
显式转换
您可以
ArgumentConverter使用@ConvertWith注释显式指定要用于某个参数, 而不是使用隐式参数转换,如下例所示.
JUnit为需要创建特定客户端的客户提供参考实现ArgumentConverter.
显式参数转换器应由测试作者实现.因此,junit-jupiter-params只提供一个显式参数转换器,它也可以作为参考实现:
JavaTimeArgumentConverter.它通过组合注释使用JavaTimeConversionPattern.
使用此转换器的测试方法:
@ParameterizedTest
@ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
assertEquals(2017, argument.getYear());
}
Run Code Online (Sandbox Code Playgroud)
JavaTimeArgumentConverter 转换器类:
package org.junit.jupiter.params.converter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalQuery;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.params.support.AnnotationConsumer;
/**
* @since 5.0
*/
class JavaTimeArgumentConverter extends SimpleArgumentConverter
implements AnnotationConsumer<JavaTimeConversionPattern> {
private static final Map<Class<?>, TemporalQuery<?>> TEMPORAL_QUERIES;
static {
Map<Class<?>, TemporalQuery<?>> queries = new LinkedHashMap<>();
queries.put(ChronoLocalDate.class, ChronoLocalDate::from);
queries.put(ChronoLocalDateTime.class, ChronoLocalDateTime::from);
queries.put(ChronoZonedDateTime.class, ChronoZonedDateTime::from);
queries.put(LocalDate.class, LocalDate::from);
queries.put(LocalDateTime.class, LocalDateTime::from);
queries.put(LocalTime.class, LocalTime::from);
queries.put(OffsetDateTime.class, OffsetDateTime::from);
queries.put(OffsetTime.class, OffsetTime::from);
queries.put(Year.class, Year::from);
queries.put(YearMonth.class, YearMonth::from);
queries.put(ZonedDateTime.class, ZonedDateTime::from);
TEMPORAL_QUERIES = Collections.unmodifiableMap(queries);
}
private String pattern;
@Override
public void accept(JavaTimeConversionPattern annotation) {
pattern = annotation.value();
}
@Override
public Object convert(Object input, Class<?> targetClass) throws ArgumentConversionException {
if (!TEMPORAL_QUERIES.containsKey(targetClass)) {
throw new ArgumentConversionException("Cannot convert to " + targetClass.getName() + ": " + input);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
TemporalQuery<?> temporalQuery = TEMPORAL_QUERIES.get(targetClass);
return formatter.parse(input.toString(), temporalQuery);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2977 次 |
| 最近记录: |