Skip to content

JUnit 5 参数化测试完全指南

同一测试逻辑使用不同数据多次执行,实现数据驱动测试


概述

参数化测试允许同一测试逻辑使用不同数据多次执行,实现数据驱动的测试方式。

启用参数化测试

xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

@ValueSource

整数数组

java
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
@DisplayName("整数是否为正数")
void testPositiveNumbers(int number) {
    assertTrue(number > 0);
}

字符串数组

java
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "JUnit"})
@DisplayName("字符串长度测试")
void testStringLength(String word) {
    assertTrue(word.length() > 0);
}

其他类型

java
@ValueSource(longs = {1L, 2L, 3L})
@ValueSource(doubles = {0.1, 0.2, 0.3})
@ValueSource(classes = {String.class, Integer.class})

@CsvSource

基本用法

java
@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "4, 5, 9",
    "10, 20, 30"
})
@DisplayName("加法测试:a + b = expected")
void testAddition(int a, int b, int expected) {
    assertEquals(expected, a + b);
}

空值和 null

java
@ParameterizedTest
@CsvSource({
    "'hello', 5",      // 字符串 'hello',长度 5
    "'', 0",           // 空字符串,长度 0
    ", 0"              // null 作为缺失值(需配置)
})
void testStringLength(String input, int expected) { }

CSV 文件来源

java
@ParameterizedTest
@CsvFileSource(resources = "/test-data/calculator.csv", numLinesToSkip = 1)
void testFromCsvFile(int a, int b, int expected) {
    assertEquals(expected, a + b);
}

@MethodSource

静态方法来源

java
@ParameterizedTest
@MethodSource("arithmeticProvider")
void testAddition(int a, int b, int expected) {
    assertEquals(expected, a + b);
}

// 必须返回 Stream<Arguments>
static Stream<Arguments> arithmeticProvider() {
    return Stream.of(
        Arguments.of(2, 3, 5),
        Arguments.of(10, 5, 15),
        Arguments.of(-1, 1, 0)
    );
}

外部类方法来源

java
@ParameterizedTest
@MethodSource("com.example.TestDataFactory#createOrders")
void testOrders(Order order) {
    assertNotNull(order);
}

@EnumSource

基本用法

java
@ParameterizedTest
@EnumSource(OperatingSystem.class)
void testOperatingSystems(OperatingSystem os) {
    assertNotNull(os);
}

过滤选项

java
@ParameterizedTest
@EnumSource(value = Month.class, names = {"JANUARY", "FEBRUARY", "MARCH"})
void testQuarterMonths(Month month) {
    assertTrue(month.getValue() <= 3);
}

// 排除特定值
@EnumSource(value = Month.class, names = ".+BER", mode = EnumSource.Mode.EXCLUDE)
void testNonDecember(Month month) {
    assertNotEquals(Month.DECEMBER, month);
}

空值和空来源

@NullSource / @EmptySource

java
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {"a", "b", "c"})
void testStrings(String value) {
    // 测试: null, "", "a", "b", "c"
    assertTrue(value == null || !value.isEmpty());
}

@ArgumentsSource(自定义来源)

java
public class CustomArgumentsProvider implements ArgumentsProvider {
    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of(
            Arguments.of("input1", 1),
            Arguments.of("input2", 2)
        );
    }
}

// 使用
@ParameterizedTest
@ArgumentsSource(CustomArgumentsProvider.class)
void testWithCustomSource(String input, int expected) { }

自定义参数转换器

java
public class LocalDateConverter extends SimpleArgumentConverter {
    @Override
    protected Object convert(Object input, Class<?> targetType) {
        if (targetType == LocalDate.class && input instanceof String) {
            return LocalDate.parse((String) input);
        }
        throw new IllegalArgumentException("Cannot convert");
    }
}

// 使用
@ParameterizedTest
@CsvSource({"2024-01-01, NEW_YEAR", "2024-12-25, CHRISTMAS"})
void testHoliday(
        @ConvertWith(LocalDateConverter.class) LocalDate date,
        HolidayType type) { }

@DisplayName 模板

java
@ParameterizedTest(name = "计算 {0} + {1} 应等于 {2}")
@CsvSource({"1, 2, 3", "4, 5, 9"})
void testAddition(int a, int b, int expected) {
    assertEquals(expected, a + b);
}

输出示例:

✔ 计算 1 + 2 应等于 3
✔ 计算 4 + 5 应等于 9

最佳实践

  1. 使用有意义的显示名称

    java
    @DisplayName("用户名 [{0}] 验证应 {1}")
  2. 将复杂数据封装为对象

    java
    static Stream&lt;OrderTestCase&gt; orderTestCases() {
        return Stream.of(
            new OrderTestCase("正常商品", 100.0, 10.0, 110.0),
            new OrderTestCase("折扣商品", 100.0, 20.0, 80.0)
        );
    }
    
    record OrderTestCase(String name, double price, double shipping, double total) { }
  3. 避免过多参数(建议 ≤ 5 个)

  4. 结合条件执行

    java
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    @EnabledOnOs(OS.LINUX)  // 仅 Linux 执行
    void linuxOnly(int value) { }

相关页面

  • **Java\JUnit5-完全指南** - 返回总览
  • **Java\JUnit5-注解体系** - @ParameterizedTest 注解
  • **Java\JUnit5-断言机制** - 断言在参数化测试中的应用
  • **Java\Mockito-完全指南** - Mock 与参数化结合

#标签 #junit #参数化 #parameterized #testing #data-driven

Knowledge4J — Java 知识库