【问题标题】:Parameterize both class and tests in JUnit 5在 JUnit 5 中参数化类和测试
【发布时间】:2018-10-01 20:01:56
【问题描述】:

有没有办法同时参数化测试类(就像您可以在 JUnit 4 中使用 Parameterized@Parameters 一样)和测试方法(就像您可以在 JUnit 4 中使用 JUnitParams 或在 JUnit 5 中使用 @ParameterizedTest )?最后需要得到参数的笛卡尔积。

使用所需方法对java.nio.ByteBuffer 进行部分测试的示例:

public class ByteBufferTest {
    private static final int BUFFER_SIZE = 16384;
    private final ByteOrder byteOrder;
    private ByteBuffer sut;

    @Factory(dataProvider = "byteOrders")
    public ByteBufferTest(ByteOrder byteOrder) {
        this.byteOrder = byteOrder;
    }

    @DataProvider
    public static Object[][] byteOrders() {
        return new Object[][] {
                {ByteOrder.BIG_ENDIAN},
                {ByteOrder.LITTLE_ENDIAN}
        };
    }

    @BeforeMethod
    public void setUp() {
        sut = ByteBuffer.allocate(BUFFER_SIZE);
        sut.order(byteOrder);
    }

    @Test(dataProvider = "validPositions")
    public void position(int position) {
        System.out.println(byteOrder + " position " + position);
        sut.position(position);
        assertThat(sut.position()).isEqualTo(position);
    }

    @DataProvider
    public static Object[][] validPositions() {
        return new Object[][] {{0}, {1}, {BUFFER_SIZE - 1}};
    }

    @Test(dataProvider = "intPositionsAndValues")
    public void putInt(int position, int value, byte[] expected) {
        System.out.println(byteOrder + " position " + position + " value " + value);
        sut.putInt(position, value);
        assertThat(sut.array())
                .contains(expected[0], atIndex(position))
                .contains(expected[1], atIndex(position + 1))
                .contains(expected[2], atIndex(position + 2))
                .contains(expected[3], atIndex(position + 3));
    }

    @DataProvider
    public Object[][] intPositionsAndValues() {
        if (byteOrder == ByteOrder.BIG_ENDIAN) {
            return new Object[][]{
                    {0, 0, new byte[4]},
                    {5, 123456789, new byte[] {0x07, 0x5B, (byte) 0xCD, 0x15}},
            };
        } else {
            return new Object[][]{
                    {0, 0, new byte[4]},
                    {5, 123456789, new byte[] {0x15, (byte) 0xCD, 0x5B, 0x07}},
            };
        }
    }
}

它产生:

LITTLE_ENDIAN position 0
LITTLE_ENDIAN position 1
LITTLE_ENDIAN position 16383
BIG_ENDIAN position 0
BIG_ENDIAN position 1
BIG_ENDIAN position 16383
LITTLE_ENDIAN position 0 value 0
LITTLE_ENDIAN position 5 value 123456789
BIG_ENDIAN position 0 value 0
BIG_ENDIAN position 5 value 123456789

我们正在考虑从 TestNG 迁移到 JUnit 5,但我们经常使用这种东西。在上面的例子中使用字节顺序作为类级参数并非巧合:我们经常需要对各种二进制数据处理器进行测试,其中测试构造函数将采用字节/位顺序参数,我们运行每个测试大端和小端。

我正在考虑为此创建一个扩展程序,然后使用ExtendWith,但也许有一个现有的扩展程序或我错过的开箱即用的东西?

【问题讨论】:

  • 为什么不将它们中的每一个合并到Object[] 方法中的Object[] 参数中?
  • @daniu,因为通常不止一种方法。这显然违反了 DRY 原则。
  • 怎么样?创建一个CombinedDataProvider,它接受任意数量的Supplier<Object[]>s 并将它们合并到一个矩阵中,看起来非常简单且非RY。您可以使用@Rule 添加它。
  • @daniu,很有趣。我会研究它,但它还没有完全干燥:如果我还有一个类级参数,我必须将它添加到每个方法的签名中。对我来说,它看起来有点过于复杂,所有的规则和供应商。对于这样一个看似简单的任务,管道太多了。
  • 目前,类不能参数化,但有计划改变:github.com/junit-team/junit5/issues/878#issuecomment-354544841

标签: java junit5 parameterized-unit-test


【解决方案1】:

JUnit Jupiter(原版)

您可以组合多个来源,例如@MethodSource。基于您的 TestNG 示例:

class ExampleTest {

    @ParameterizedTest
    @MethodSource("args")
    void test(String classParameter, String testParameter) {
        System.out.println(classParameter + " " + testParameter);
    }

    static Stream<Arguments> args() {
        return classParameters().flatMap(
                classParameter -> testParameters().map(
                        testParameter -> Arguments.of(classParameter, testParameter)));
    }

    static Stream<String> classParameters() {
        return Stream.of("classParam1", "classParam2");
    }

    static Stream<String> testParameters() {
        return Stream.of("testParam1", "testParam2");
    }

}

这会产生:

classParam1 testParam1
classParam1 testParam2
classParam2 testParam1
classParam2 testParam2

根据OP 的要求,这里是“一个至少有两种不同参数集的测试方法的例子”:

class ExampleTest {

    static Stream<String> classParams() {
        return Stream.of("classParam1", "classParam2", "classParam3");
    }

    static Stream<Arguments> withClassParams(List<?> methodParams) {
        return classParams().flatMap(
                classParam -> methodParams.stream().map(
                        methodParam -> Arguments.of(classParam, methodParam)));
    }

    @ParameterizedTest
    @MethodSource
    void booleanParams(String classParam, boolean booleanParam) {
        System.out.println(classParam + " " + booleanParam);
    }

    static Stream<Arguments> booleanParams() {
        return withClassParams(List.of(false, true));
    }

    @ParameterizedTest
    @MethodSource
    void integerParams(String classParam, int integerParam) {
        System.out.println(classParam + " " + integerParam);
    }

    static Stream<Arguments> integerParams() {
        return withClassParams(List.of(1, 2, 3, 4, 5, 6));
    }

    @ParameterizedTest
    @MethodSource
    void objectParams(String classParam, Object objectParam) {
        System.out.println(classParam + " " + objectParam);
    }

    static Stream<Arguments> objectParams() {
        return withClassParams(List.of(new Object()));
    }

}

3 个类参数加上 3 个不同类型和大小的不同方法参数,产生以下输出:

classParam1 java.lang.Object@35cabb2a
classParam2 java.lang.Object@35cabb2a
classParam3 java.lang.Object@35cabb2a
classParam1 1
classParam1 2
classParam1 3
classParam1 4
classParam1 5
classParam1 6
classParam2 1
classParam2 2
classParam2 3
classParam2 4
classParam2 5
classParam2 6
classParam3 1
classParam3 2
classParam3 3
classParam3 4
classParam3 5
classParam3 6
classParam1 false
classParam1 true
classParam2 false
classParam2 true
classParam3 false
classParam3 true

JUnit 先锋

JUnit Jupiter 有 JUnit Pioneer 扩展包。它带有@CartesianProductTest。使用上面的扩展示例:

class CartProdTest {

    @CartesianProductTest(factory = "classWithObjectParams")
    void testClassWithObject(String classParam, Object objectParam) {
        System.out.println(classParam + " " + objectParam);
    }

    static CartesianProductTest.Sets classWithObjectParams() {
        return new CartesianProductTest.Sets()
                .addAll(classParams())
                .add(new Object());
    }

    @CartesianProductTest(factory = "classWithIntegerParams")
    void testClassWithInteger(String classParam, int integerParam) {
        System.out.println(classParam + " " + integerParam);
    }

    static CartesianProductTest.Sets classWithIntegerParams() {
        return new CartesianProductTest.Sets()
                .addAll(classParams())
                .add(1, 2, 3, 4, 5, 6);
    }

    @CartesianProductTest(factory = "classWithBooleanParams")
    void testClassWithBoolean(String classParam, boolean booleanParam) {
        System.out.println(classParam + " " + booleanParam);
    }

    static CartesianProductTest.Sets classWithBooleanParams() {
        return new CartesianProductTest.Sets()
                .addAll(classParams())
                .add(false, true);
    }

    static Stream<String> classParams() {
        return Stream.of("classParam1", "classParam2", "classParam3");
    }

}

这会产生相同的输出。

【讨论】:

  • 某种作品,但远非优雅,除非可能是一个非常简单的案例,否则我不会打扰它。如果我有 10 个参数化测试怎么办?我将不得不重复同样的技巧 10 次。一点也不干。
  • @SergeiTachenov 您可以将不同的源(类参数、测试参数等)提取到单独的类中并重用它们。我看不出这如何违反 DRY 原则。或者你是什么意思?
  • 一类,十种测试方法。方法级参数不同(类型不同,数量不同,集合不同),类级参数相同。你会怎么做?您能否编辑您的答案以提供一个示例,其中包含至少两种具有不同参数集的测试方法?
  • @SergeiTachenov 所以有一组固定的类参数(例如 cp1、cp2、cp3),但有多个不同的方法参数集(例如 mp1-1、mp1-2、mp1-3 和 mp2-1, mp2-2、mp2-3)?那么你想用每个方法参数测试每个类参数?
  • 是的,这样好多了。我没有想过从参数方法中提取这个笛卡尔逻辑。但是,该示例仍然不够通用,因为每个测试只有一个方法级参数。但从这里进一步概括应该不难。
猜你喜欢
  • 1970-01-01
  • 2022-10-06
  • 1970-01-01
  • 1970-01-01
  • 2019-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多