mcpField
U
unit-test-parameterized

unit-test-parameterized

github.com/giuseppe-trisciuoglio测试与质量3653.8k 次安装7 周前更新
代码仓库

关于 unit-test-parameterized

面向 Java 开发者的 JUnit 5 参数化测试技能,介绍简单值、表格数据、复杂对象和枚举等数据源,并涵盖显示名称与验证方法。

主要功能

  1. 01六种 JUnit 5 数据源的用法
  2. 02设置易读的参数化测试名称
  3. 03匹配数据源与方法参数类型
  4. 04边界值与场景测试实践
  5. 05测试执行与依赖检查

适用场景

  1. 01测试多组输入组合
  2. 02检查 Java 边界值
  3. 03合并重复的测试逻辑
  4. 04覆盖枚举与对象场景

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-parameterized

安装后在 Claude Code 中输入/unit-test-parameterized确认已加载。

详细介绍

使用 JUnit 5 编写参数化单元测试

概述

介绍使用 JUnit 5 在 Java 中编写参数化单元测试的模式。涵盖 @ValueSource、@CsvSource、@MethodSource、@EnumSource、@ArgumentsSource 和自定义显示名称。通过使用多个输入值运行相同的测试逻辑,减少测试重复。

适用场景

  • 使用多个输入组合编写 JUnit 测试
  • 在 Java 中实现数据驱动测试
  • 使用不同的值运行同一测试(边界分析)
  • 在单个测试方法中测试多个场景

操作说明

  1. 添加依赖项:确保测试类路径中包含 junit-jupiter-params(已包含在 junit-jupiter 中)
  2. 选择数据源:简单值使用 @ValueSource,表格数据使用 @CsvSource,复杂对象使用 @MethodSource
  3. 匹配参数:测试方法参数必须与数据源类型匹配
  4. 设置显示名称:使用 name = "{0}..." 生成易读的输出
  5. 验证:运行 ./gradlew test --info 或 mvn test,并确认所有参数组合都已执行

示例

Maven / Gradle 依赖项

JUnit 5 参数化测试需要 junit-jupiter(包含 params)。添加 assertj-core 用于断言:


<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>
// Gradle
testImplementation("org.junit.jupiter:junit-jupiter")
@ValueSource — 简单值
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.*;

@ParameterizedTest
@ValueSource(strings = {"hello", "world", "test"})
void shouldCapitalizeAllStrings(String input) {
  assertThat(StringUtils.capitalize(input)).isNotEmpty();
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void shouldBePositive(int number) {
  assertThat(number).isPositive();
}

@ParameterizedTest
@ValueSource(ints = {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE})
void shouldHandleBoundaryValues(int value) {
  assertThat(Math.incrementExact(value)).isGreaterThan(value);
}
@CsvSource — 表格数据
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@ParameterizedTest
@CsvSource({
  "[email protected], true",
  "[email protected],     true",
  "invalid-email,     false",
  "user@,             false",
  "@example.com,       false"
})
void shouldValidateEmailAddresses(String email, boolean expected) {
  assertThat(UserValidator.isValidEmail(email)).isEqualTo(expected);
}
@MethodSource — 复杂数据
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

@ParameterizedTest
@MethodSource("additionTestCases")
void shouldAddNumbersCorrectly(int a, int b, int expected) {
  assertThat(Calculator.add(a, b)).isEqualTo(expected);
}

static Stream<Arguments> additionTestCases() {
  return Stream.of(
    Arguments.of(1, 2, 3),
    Arguments.of(0, 0, 0),
    Arguments.of(-1, 1, 0),
    Arguments.of(100, 200, 300)
  );
}
@EnumSource — 枚举值
@ParameterizedTest
@EnumSource(Status.class)
void shouldHandleAllStatuses(Status status) {
  assertThat(status).isNotNull();
}

@ParameterizedTest
@EnumSource(value = Status.class, names = {"ACTIVE", "INACTIVE"})
void shouldHandleSpecificStatuses(Status status) {
  assertThat(status).isIn(Status.ACTIVE, Status.INACTIVE);
}
自定义显示名称
@ParameterizedTest(name = "Discount of {0}% should be calculated correctly")
@ValueSource(ints = {5, 10, 15, 20})
void shouldApplyDiscount(int discountPercent) {
  double result = DiscountCalculator.apply(100.0, discountPercent);
  assertThat(result).isEqualTo(100.0 * (1 - discountPercent / 100.0));
}
自定义 ArgumentsProvider
class RangeValidatorProvider implements ArgumentsProvider {
  @Override
  public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
    return Stream.of(
      Arguments.of(0, 0, 100, true),
      Arguments.of(50, 0, 100, true),
      Arguments.of(-1, 0, 100, false),
      Arguments.of(101, 0, 100, false)
    );
  }
}

@ParameterizedTest
@ArgumentsSource(RangeValidatorProvider.class)
void shouldValidateRange(int value, int min, int max, boolean expected) {
  assertThat(RangeValidator.isInRange(value, min, max)).isEqualTo(expected);
}
错误条件测试
@ParameterizedTest
@ValueSource(strings = {"", " ", null})
void shouldThrowExceptionForInvalidInput(String input) {
  assertThatThrownBy(() -> Parser.parse(input))
    .isInstanceOf(IllegalArgumentException.class);
}

最佳实践

  • 使用描述性的显示名称:使用 name = "{0}..." 生成易读的输出
  • 测试边界值:包括最小值、最大值、零和边界情况
  • 保持测试逻辑聚焦:每组参数只使用一个断言
  • 复杂对象使用 @MethodSource,表格数据使用 @CsvSource
  • 按逻辑组织测试数据——将相关场景归为一组

限制与警告

  • 参数数量必须匹配:数据源提供的参数数量必须与测试方法签名匹配
  • @ValueSource 的限制:仅支持基本类型、字符串和枚举,不能直接传入对象或 null
  • CSV 转义:在 @CsvSource 中,包含逗号的字符串必须使用单引号
  • @MethodSource 的可见性:工厂方法必须是同一测试类中的静态方法
  • 显示名称占位符:使用 {0}、{1} 等引用参数
  • 执行次数:每组参数都会作为单独的测试调用运行

参考资料

评论

支持 Markdown