gpt4 book ai didi

java - 如何在JUnit 5中实现JUnit 4参数化测试?

转载 作者:行者123 更新时间:2023-12-04 20:07:45 26 4
gpt4 key购买 nike

在JUnit 4中,使用 @Parameterized 批注很容易在一系列类中测试不变式。关键是要针对单个参数列表运行一组测试。

如何在不使用JUnit-vintage的情况下在JUnit 5中复制它?

@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(){
...
}
}

最佳答案

JUnit 5中的参数化测试功能无法提供与JUnit 4完全相同的功能。
引入了具有更大灵活性的新功能...但是它也丢失了JUnit4功能,在该功能中,参数化测试类在类级别使用适用于该类所有测试方法的参数化夹具/断言。
因此需要通过指定“输入”为每种测试方法定义@ParameterizedTest
除了上述不足之外,我还将介绍两个版本之间的主要区别以及如何在JUnit 5中使用参数化测试。

TL; DR

要编写参数化测试以按大小写指定值以测试您的问题,
org.junit.jupiter.params.provider.MethodSource 应该可以完成这项工作。

@MethodSource allows you to refer to one or more methods of the test class. Each method must return a Stream, Iterable, Iterator, or array of arguments. In addition, each method must not accept any arguments. By default such methods must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

If you only need a single parameter, you can return instances of the parameter type directly as demonstrated by the following example.



与JUnit 4一样, @MethodSource依赖于工厂方法,也可以用于指定多个参数的测试方法。

在JUnit 5中,它是编写最接近JUnit 4的参数化测试的方式。

JUnit 4:
@Parameters
public static Collection<Object[]> data() {

JUnit 5:
private static Stream<Arguments> data() {

主要改进:
  • Collection<Object[]>成为Stream<Arguments>,可提供更大的灵活性。
  • 将工厂方法绑定(bind)到测试方法的方式略有不同。
    现在它更短,更不容易出错:不再需要创建构造函数并声明字段来设置每个参数的值。直接在测试方法的参数上完成源的绑定(bind)。
  • 对于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));
    }
    }

    要将现有的参数化测试从JUnit 4升级到JUnit 5,可以考虑使用@MethodSource

    总结
    @MethodSource有一些优点,但也有一些缺点。
    JUnit 5中引入了指定参数化测试源的新方法。
    在这里,我希望能提供一些有关它们的附加信息(到目前为止还很详尽),以使人们对如何以一般方式进行处理有一个广泛的了解。

    简介

    JUnit 5通过以下术语引入了parameterized tests feature:

    Parameterized tests make it possible to run a test multiple times with different arguments. They are declared just like regular @Test methods but use the @ParameterizedTest annotation instead. In addition, you must declare at least one source that will provide the arguments for each invocation.



    依赖性要求
    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>

    可用于创建数据的源

    与JUnit 4相反,JUnit 5提供了多种功能和工件来编写参数化测试
    支持的方式通常取决于您要使用的数据源。

    这是框架提出的并在documentation中描述的源类型:
  • @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是一个很好的选择。
    为此,定义一个方法,该方法返回每种情况下的值的流,并使用@MethodSource("methodName")注释测试方法,其中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());
    }

    }

    如果您在测试方法中具有多个方法参数,并且想要使用任何类型的作为源,那么@MethodSource也是一个很好的选择。
    为此,定义一个方法以针对每种情况返回org.junit.jupiter.params.provider.Arguments流。

    例如,您可以编写:
    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"));
    }
    }

    2)@ValueSource
    如果您在测试方法中具有单方法参数,并且您可以从代表这些内置类型之一(字符串,整数,长整数, double )@ValueSource的参数源。
    @ValueSource确实定义了以下属性:
    String[] strings() default {};
    int[] ints() default {};
    long[] longs() default {};
    double[] doubles() default {};

    例如,您可以通过以下方式使用它:
    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);
    }

    }

    注意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);
    }

    }

    @CsvSource VS @MethodSource

    这些源类型有一个非常经典的要求:在测试方法中从源映射到多个方法参数
    但是他们的方法不同。
    @CsvSource具有一些优点:它更清晰,更短。
    实际上,参数是在测试方法的上方定义的,不需要创建夹具方法,该方法还可能会生成“未使用”的警告。
    但是它在映射类型方面也有一个重要的限制。
    您必须提供一个String数组。该框架提供了转换功能,但受到限制。

    总而言之,虽然作为源提供的String和测试方法的参数具有相同的类型(String-> String)或依赖于内置转换(例如String-> int),但@CsvSource似乎是使用方式。

    并非如此,您必须在
    通过为框架未使用@CsvSource和工厂方法执行的转换创建自定义转换器(ArgumentConverter子类),从而保持@MethodSource的灵活性。
    返回Stream<Arguments>。它具有上述缺点,但从源到参数的任何类型的开箱即用映射也具有很大的好处。

    参数转换

    关于源(例如@CsvSource@ValueSource)和测试方法的参数之间的映射,如所看到的,如果类型不同,则框架允许进行一些转换。

    Here展示了两种类型的转换:

    3.13.3. Argument Conversion

    Implicit Conversion

    To support use cases like @CsvSource, JUnit Jupiter provides a number of built-in implicit type converters. The conversion process depends on the declared type of each method parameter.

    .....

    String instances are currently implicitly converted to the following target types.

    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
    .....


    例如,在前面的示例中,在源的String和定义为参数的int之间进行了隐式转换:
    @CsvSource({ "12,3,4", "12,2,6" })
    public void divideTest(int n, int d, int q) {
    Assertions.assertEquals(q, n / d);
    }

    在这里,完成了从String源到LocalDate参数的隐式转换:
    @ParameterizedTest
    @ValueSource(strings = { "2018-01-01", "2018-02-01", "2018-03-01" })
    void testWithValueSource(LocalDate date) {
    Assertions.assertTrue(date.getYear() == 2018);
    }

    如果对于两种类型,框架不提供任何转换,
    对于自定义类型,您应该使用ArgumentConverter

    Explicit Conversion

    Instead of using implicit argument conversion you may explicitly specify an ArgumentConverter to use for a certain parameter using the @ConvertWith annotation like in the following example.



    JUnit为需要创建特定ArgumentConverter的客户端提供引用实现。

    Explicit argument converters are meant to be implemented by test authors. Thus, junit-jupiter-params only provides a single explicit argument converter that may also serve as a reference implementation: JavaTimeArgumentConverter. It is used via the composed annotation JavaTimeConversionPattern.



    使用此转换器的测试方法:
    @ParameterizedTest
    @ValueSource(strings = { "01.01.2017", "31.12.2017" })
    void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
    assertEquals(2017, argument.getYear());
    }
    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);
    }

    }

    关于java - 如何在JUnit 5中实现JUnit 4参数化测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46897134/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com