当前位置: 首页 > 知识库问答 >
问题:

如何在JUnit5中实现JUnit4参数化测试?

徐鸿文
2023-03-14

在JUnit 4中,通过使用@Parameterize注释很容易跨一堆类测试不变量。关键是一组测试正在针对单个参数列表运行。

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

共有1个答案

孙斌
2023-03-14

JUnit 5中的参数化测试特性并不提供与JUnit 4完全相同的特性。< br >引入了更加灵活的新功能...但是它也失去了JUnit4的特性,参数化的测试类在类级别使用参数化的fixture/assertion,用于类的所有测试方法。< br >通过指定“输入”为每个测试方法定义< code>@ParameterizedTest是非常必要的。< br >除此之外,我将介绍两个版本之间的主要区别,以及如何在JUnit 5中使用参数化测试。

断续器

要编写一个参数化的测试,通过case指定一个值来测试您的问题,< code > org . JUnit . Jupiter . params . provider . method source 应该可以完成这项工作。

@MethodSource允许您引用测试类的一个或多个方法。每个方法都必须返回可迭代迭代或参数数组。此外,每个方法不得接受任何参数。默认情况下,此类方法必须是静态的,除非测试类使用 @TestInstance(Lifecycle.PER_CLASS) 进行批注。

如果只需要一个参数,则可以直接返回参数类型的实例,如以下示例所示。

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

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

JUnit 4:

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

JUnit 5:

private static Stream<Arguments> data() {

主要改进:

>

  • <代码>集合

    将工厂方法绑定到测试方法的方式略有不同。< br >现在它更短,更不容易出错:不再需要创建构造函数和声明字段来设置每个参数的值。源代码的绑定直接在测试方法的参数上完成。

    对于 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,@omeodSource是一个需要考虑的候选者。

    总结

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

    可用于创建数据的源

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

    以下是框架提出并在文档中描述的源类型:

    • @ValueSource
    • @E num Source
    • @方法源
    • @Csv源代码
    • @Csv文件源
    • @参数源

    以下是我在JUnit 5中实际使用的三个主要源代码,我将介绍:

    • @MethodSource
    • @ValueSource
    • @CsvSource

    我认为它们是我编写参数化测试的基础。他们应该允许在JUnit 5中编写,你描述的JUnit 4测试类型。< br> @EnumSource 、< code>@ArgumentsSource和< code>@CsvFileSource当然会有所帮助,但它们更专业。

    < code>@MethodSource 、< code>@ValueSource和< code>@CsvSource的演示

    1) @MethodSource

    这种类型的源需要定义一个工厂方法。< br >但它也提供了很大的灵活性。< br >

    在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也是一个非常好的候选方法。
    为了实现它,定义一个方法,为每个要测试的案例返回一个流。

    例如,您可以编写:

    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

    如果您在测试方法中有一个方法参数,并且您可以从这些内置类型(字符串、整数、长、双精度)之一表示参数的源,@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 数组中指定每种情况。
    每个事例的值都用逗号分隔。

    像< code>@ValueSource一样,方法的源和参数之间的映射可以在两个不同的类型之间完成。< br >下面是一个说明这一点的示例:

    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@MethodSource

    这些源类型满足一个非常经典的要求:从源映射到测试方法中的多个方法参数
    但他们的方法不同。

    @CsvSource有一些优点 :它更清晰,更短。
    实际上,参数的定义刚好高于测试方法,不需要创建可能另外产生“未使用”警告的夹具方法。
    但它也有一个关于映射类型的重要限制。
    您必须提供字符串数组。该框架提供转换功能,但功能有限。

    总而言之,作为源提供的<code>字符串

    由于情况并非如此,您必须做出选择,要么通过为框架未执行的转换创建自定义转换器(ArgumentConverter 子类)来保持@CsvSource的灵活性,要么使用@MethodSource返回 Stream 的工厂方法

    论元转换

    关于源代码(@CsvSource@ValueSource)与测试方法参数之间的映射,如图所示,如果类型不相同,框架允许进行一些转换。

    下面是两种类型转换的演示:

    3.13.3.变元转换

    隐式转换

    为了支持@CsvSource等用例,JUnit Jupiter提供了许多内置的隐式类型转换器。转换过程取决于每个方法参数的声明类型。

    ……

    String实例当前隐式转换为以下目标类型。

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

    在这里,从字符串源到本地日期参数的隐式转换完成:

    @ParameterizedTest
    @ValueSource(strings = { "2018-01-01", "2018-02-01", "2018-03-01" })
    void testWithValueSource(LocalDate date) {
        Assertions.assertTrue(date.getYear() == 2018);
    }
    

    如果对于两种类型,框架没有提供转换,这是自定义类型的情况,则应使用ArgumentConverter

    显式转换

    您可以不使用隐式参数转换,而是使用< code>@ConvertWith批注显式指定用于某个参数的< code>ArgumentConverter,如下例所示。

    JUnit为需要创建特定ArgumentConverter的客户端提供了参考实现。

    显式参数转换器旨在由测试作者实现。因此,junit-jupiter-params 仅提供一个显式参数转换器,该转换器也可以用作参考实现:JavaTime参数转换器。它通过组合注释 Java 时间转换模式使用

    使用该转换器的测试方法:

    @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);
        }
    
    }
    

  •  类似资料:
    • 主要内容:1 参数化测试的介绍,2 使用@Parameter进行字段注入而不是构造函数,3 使用单个参数进行测试,4 识别单个测试用例1 参数化测试的介绍 自定义流道参数化实现参数化测试。运行参数化测试类时,将为测试方法和测试数据元素的叉积创建实例。 例如,要测试斐波那契函数,请编写: FibonacciTest的每个实例都将使用二元参数构造函数和方法中的数据值构造 @Parameters 。 2 使用@Parameter进行字段注入而不是构造函数 也可以将数据值直接注入字段中,而无需使用@Pa

    • 问题内容: 在JUnit 4中,使用批注很容易在多个类中测试不变式。关键是要针对单个参数列表运行一组测试。 如何在不使用JUnit-vintage的情况下在JUnit 5中复制它? 不适用于测试课程。听起来似乎很合适,但是注释的目标也是一种方法。 此类JUnit 4测试的示例是: 问题答案: JUnit 5中的参数化测试功能所提供的 功能与JUnit 4所提供的功能完全不同。引入了具有更大灵活性的

    • 我有一个接口,例如: 以及我试图测试的该接口的几个实现(例如、、)。 我的许多测试方法实际上是为了确保接口被正确实现,因此在每个实现中都是重复的。在JUnit3中,一个常见的解决方案是创建一个基类(扩展),然后由每个实现类对其进行子类化。但是对于JUnit4来说,这是正确的方法吗? 在(我相信)优先顺序的升序中可能的选择: > 剪切'n'粘贴重复的测试方法。一点也不枯燥,但我想测试中的担忧比生产代

    • 我有一个工作环境,包括我不管理的bom和JUnit5测试,除非我从如果我从它们不会被maven surefire插件拾取。我阅读了许多部分,并在一个较小的项目中进行了测试,它的工作和拾取,我不知道该在这篇文章中包含什么,因此您有足够的信息来查看问题所在。此外,如果有人能解释我阅读的导入的幕后内容,JUnit 4和5都需要使用surefire即 与版本 我很感激。 注意到 < li >我的测试都在s

    • 我正在尝试从CSV文件运行参数化测试。 如果我只使用这样的CSVSource,它就会起作用: 但如果我从一个文件中尝试同样的方法,它将不起作用: 我也尝试过使用硬路径访问我的文件,但对于Eclipse中的文件测试,我总是能得到消息 找不到使用测试runnter“JUnit 5”的测试。 JUnit期望文件在哪里? 以下是我的依赖关系: 有人知道我可能遗漏了什么或者错误在哪里吗? 提前谢谢你 保罗

    • 我试图定义一个,如示例MockitoExtension所提供的,但无法成功地使用参数化的类实例。 期待着关于如何用在中实现接口的类的参数化实例测试接口的建议。