JUnit 5中如何实现多参数源组合的@ParameterizedTests?
问题描述
我原本想写出这样的测试代码:
import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; class HsbColorTest { @ParameterizedTest @ValueSource(floats = {0, 100, 200, 360}) // 对应色调hue (0-360) @ValueSource(floats = {0, 35, 100}) // 对应饱和度saturation (0-100) @ValueSource(floats = {0, 35, 100}) // 对应亮度brightness (0-100) void create_withValidArgs_createsExpectedColor(float hue, float saturation, float brightness) { HsbColor hsbColor = new HsbColor(hue, saturation, brightness); assertEquals(hue, hsbColor.getHue()); assertEquals(saturation, hsbColor.getSaturation()); assertEquals(brightness, hsbColor.getBrightness()); } }
但这段代码无法编译,因为@ValueSource不是可重复注解(@Repeatable)。
在JUnit 5里,我该怎么实现类似的效果?
我试过用@CsvSource的方式:
@ParameterizedTest @CsvSource({ "0, 0, 0", "0, 35, 35", "0, 100, 100", "100, 0, 0", "100, 35, 35", "100, 100, 100", "200, 0, 0", "200, 35, 35", "200, 100, 100", "360, 0, 0", "360, 35, 35", "360, 100, 100" }) void create_withValidArgs_createsExpectedColor(float hue, float saturation, float brightness) { HsbColor hsbColor = new HsbColor(hue, saturation, brightness); assertEquals(hue, hsbColor.getHue()); assertEquals(saturation, hsbColor.getSaturation()); assertEquals(brightness, hsbColor.getBrightness()); }
但这种写法太繁琐,可读性也差。
我也考虑过拆成三个独立的测试,但这样没法单独验证某一类参数的有效性——比如一个验证合法hue值的测试,实际上也会同时检查另外两个参数是否合法,否则这个“色调测试”会直接失败。
补充说明:HsbColor是一个简单的不可变数据类,代码如下:
public class HsbColor { private final float hue; private final float saturation; private final float brightness; public HsbColor(float hue, float saturation, float brightness) { if (hue < 0 || hue > 360) { throw new IllegalArgumentException("Hue must be between 0 and 360"); } if (saturation < 0 || saturation > 100) { throw new IllegalArgumentException("Saturation must be between 0 and 100"); } if (brightness < 0 || brightness > 100) { throw new IllegalArgumentException("Brightness must be between 0 and 100"); } this.hue = hue; this.saturation = saturation; this.brightness = brightness; } public float getHue() { return hue; } public float getSaturation() { return saturation; } public float getBrightness() { return brightness; } }
解决方案
方案一:使用@MethodSource生成参数组合
通过编写静态方法生成所有合法参数组合,将参数定义与测试逻辑分离,可读性和可维护性更强:
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import java.util.stream.Stream; class HsbColorTest { static Stream<Object[]> validColorParams() { // 定义各类参数的合法值 float[] hues = {0, 100, 200, 360}; float[] saturations = {0, 35, 100}; float[] brightnesses = {0, 35, 100}; // 生成所有参数组合 return Stream.of(hues) .flatMap(hue -> Stream.of(saturations) .flatMap(saturation -> Stream.of(brightnesses) .map(brightness -> new Object[]{hue, saturation, brightness}))); } @ParameterizedTest @MethodSource("validColorParams") void create_withValidArgs_createsExpectedColor(float hue, float saturation, float brightness) { HsbColor hsbColor = new HsbColor(hue, saturation, brightness); assertEquals(hue, hsbColor.getHue()); assertEquals(saturation, hsbColor.getSaturation()); assertEquals(brightness, hsbColor.getBrightness()); } }
后续修改参数范围时,只需调整对应数组即可,无需改动测试逻辑。
方案二:拆分测试,固定其他参数验证单一维度
如果需要单独验证某一类参数的合法性,可以固定另外两个参数为合法默认值,只变化目标参数:
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class HsbColorTest { // 验证合法hue值,固定饱和度和亮度为中间值 @ParameterizedTest @ValueSource(floats = {0, 100, 200, 360}) void create_withValidHue_createsColor(float hue) { HsbColor hsbColor = new HsbColor(hue, 50, 50); assertEquals(hue, hsbColor.getHue()); } // 验证合法saturation值,固定色调和亮度为中间值 @ParameterizedTest @ValueSource(floats = {0, 35, 100}) void create_withValidSaturation_createsColor(float saturation) { HsbColor hsbColor = new HsbColor(180, saturation, 50); assertEquals(saturation, hsbColor.getSaturation()); } // 验证合法brightness值,固定色调和饱和度为中间值 @ParameterizedTest @ValueSource(floats = {0, 35, 100}) void create_withValidBrightness_createsColor(float brightness) { HsbColor hsbColor = new HsbColor(180, 50, brightness); assertEquals(brightness, hsbColor.getBrightness()); } // 补充关键组合测试,覆盖边界值等核心场景 @ParameterizedTest @CsvSource({ "0, 0, 0", "360, 100, 100", "180, 50, 50" }) void create_withKeyCombinations_createsColor(float hue, float saturation, float brightness) { HsbColor hsbColor = new HsbColor(hue, saturation, brightness); assertEquals(hue, hsbColor.getHue()); assertEquals(saturation, hsbColor.getSaturation()); assertEquals(brightness, hsbColor.getBrightness()); } }
这种方式既能单独验证每个参数的合法性,又能通过少量关键组合测试覆盖多参数场景,避免全量组合的冗余。
方案三:使用@ArgumentsSource自定义参数提供器
如果参数生成逻辑复杂且需要复用,可以自定义参数提供器类:
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import java.util.stream.Stream; class ValidColorArgumentsProvider implements ArgumentsProvider { @Override public Stream<? extends Arguments> provideArguments(ExtensionContext context) { float[] hues = {0, 100, 200, 360}; float[] saturations = {0, 35, 100}; float[] brightnesses = {0, 35, 100}; return Stream.of(hues) .flatMap(hue -> Stream.of(saturations) .flatMap(saturation -> Stream.of(brightnesses) .map(brightness -> Arguments.of(hue, saturation, brightness)))); } }
然后在测试中使用该提供器:
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; class HsbColorTest { @ParameterizedTest @ArgumentsSource(ValidColorArgumentsProvider.class) void create_withValidArgs_createsExpectedColor(float hue, float saturation, float brightness) { HsbColor hsbColor = new HsbColor(hue, saturation, brightness); assertEquals(hue, hsbColor.getHue()); assertEquals(saturation, hsbColor.getSaturation()); assertEquals(brightness, hsbColor.getBrightness()); } }
这种方式适合需要在多个测试类中复用相同参数生成逻辑的场景。
内容的提问来源于stack exchange,提问作者Heneh
相关产品推荐
相关产品推荐

