ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2019-10-02 14:01:35  阅读:195  来源: 互联网

标签:java junit5


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

解决方法:

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() {

主要改进:

>集合< 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));
  }
}

要将现有的参数化测试从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测试的类型.
@ EnnSource,@ ArgumentsSource和@CsvFileSource当然可以提供帮助,但它们更专业.

介绍@MethodSource,@ ValueSource和@CsvSource

1)@MethodSource

此类源需要定义工厂方法.
但它也提供了很大的灵活性.

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

如果测试方法中有一个方法参数,并且您想使用任何类型作为源,那么@MethodSource是一个非常好的候选者.
要实现它,请定义一个方法,该方法返回每个案例的值的Stream,并使用@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的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"));
    }
}

2)@ValueSource

如果测试方法中有一个方法参数,并且您可以从这些内置类型之一(String,int,long,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显示为方式使用.

事实并非如此,您必须在两者之间做出选择
通过为框架未执行的转换创建自定义转换器(ArgumentConverter子类)或使用工厂方法使用@MethodSource来保持@CsvSource的灵活性
返回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.

06009

例如,在前面的示例中,在来自source的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,junit5
来源: https://codeday.me/bug/20191002/1843216.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有