【问题标题】:Junit 5 test class structureJunit 5 测试类结构
【发布时间】:2017-11-29 11:03:40
【问题描述】:

您好,我是 JUnit 的初学者,也是一般测试的初学者。可能是想多了,但我想知道测试代码结构的良好实践。我尝试了几种方法。如有任何建议,我将不胜感激。

第一种方法:将参数、输入、输出放在类的顶部。然后很容易检查或更改整个班级的测试条件:

@TestInstance(Lifecycle.PER_CLASS)
public class App {

    private DecimalRepresentation decimal;

    private final int[] positiveArguments = { 10, 50, 2500 };
    private final int[] negativeArguments = { -10, -50, -2500 };

    @BeforeAll
    void init() {
        decimal = new DecimalRepresentation();
    }

    private Stream<Arguments> createPositiveBinaryArguments() {
        return Stream.of(Arguments.of(positiveArguments[0], "1010"),
                Arguments.of(positiveArguments[1], "110010"),
                Arguments.of(positiveArguments[2], "100111000100"));
    }

    private Stream<Arguments> createNegativeBinaryArguments() {
        return Stream.of(Arguments.of(negativeArguments[0], "11111111111111111111111111110110"),
                Arguments.of(negativeArguments[1], "11111111111111111111111111001110"),
                Arguments.of(negativeArguments[2], "11111111111111111111011000111100"));
    }

    private Stream<Arguments> createPositiveOctalArguments() {
        return Stream.of(Arguments.of(positiveArguments[0], "12"),
                Arguments.of(positiveArguments[1], "62"),
                Arguments.of(positiveArguments[2], "4704"));
    }

    private Stream<Arguments> createNegativeOctalArguments() {
        return Stream.of(Arguments.of(negativeArguments[0], "37777777766"),
                Arguments.of(negativeArguments[1], "37777777716"),
                Arguments.of(negativeArguments[2], "37777773074"));
    }

    private Stream<Arguments> createPositiveHexArguments() {
        return Stream.of(Arguments.of(positiveArguments[0], "A"),
                Arguments.of(positiveArguments[1], "32"),
                Arguments.of(positiveArguments[2], "9C4"));
    }

    private Stream<Arguments> createNegativeHexArguments() {
        return Stream.of(Arguments.of(negativeArguments[0], "FFFFFFF6"),
                Arguments.of(negativeArguments[1], "FFFFFFCE"),
                Arguments.of(negativeArguments[2], "FFFFF63C"));
    }

    @ParameterizedTest
    @ValueSource(strings = { "0" })
    void shouldReturnZeroIfValueNotSpecified(String zero) {
        assertEquals(zero, decimal.toBinary());
        assertEquals(zero, decimal.toOctal());
        assertEquals(zero, decimal.toHex());
    }

    @ParameterizedTest
    @MethodSource("createPositiveBinaryArguments")
    void shouldReturnExpectedValuesForPositiveNumbersBin(int positiveNumber, String bitRepresentation) {
        decimal.setNumber(positiveNumber);
        assertEquals(bitRepresentation, decimal.toBinary());
    }

    @ParameterizedTest
    @MethodSource("createNegativeBinaryArguments")
    void shouldReturnExpectedValuesForNegativeNumbersBin(int negativeNumber, String bitRepresentation) {
        decimal.setNumber(negativeNumber);
        assertEquals(bitRepresentation, decimal.toBinary());
    }

    @ParameterizedTest
    @MethodSource("createPositiveOctalArguments")
    void shouldReturnExpectedValuesForPositiveNumbersOrc(int positiveNumber, String bitRepresentation) {
        decimal.setNumber(positiveNumber);
        assertEquals(bitRepresentation, decimal.toOctal());
    }

    @ParameterizedTest
    @MethodSource("createNegativeOctalArguments")
    void shouldReturnExpectedValuesForNegativeNumbersOct(int negativeNumber, String bitRepresentation) {
        decimal.setNumber(negativeNumber);
        assertEquals(bitRepresentation, decimal.toOctal());
    }

    @ParameterizedTest
    @MethodSource("createPositiveHexArguments")
    void shouldReturnExpectedValuesForPositiveNumbersHex(int positiveNumber, String bitRepresentation) {
        decimal.setNumber(positiveNumber);
        assertEquals(bitRepresentation, decimal.toHex());
    }

    @ParameterizedTest
    @MethodSource("createNegativeHexArguments")
    void shouldReturnExpectedValuesForNegativeNumbersHex(int negativeNumber, String bitRepresentation) {
        decimal.setNumber(negativeNumber);
        assertEquals(bitRepresentation, decimal.toHex());
    }
}

我知道我可以否定 positiveArguments 而不是创建另一个数组,但我想保持清楚,也许将来我会区分它们。

第二种方法:将每种转换类型组织成嵌套类。阅读测试更清楚,但缺点是在检查/更改测试参数的情况下滚动文件/类,因为我不能使用外部类方法源(或者我不知道如何(?)):

@TestInstance(Lifecycle.PER_CLASS)
public class DecimalRepresentationTest {

    private DecimalRepresentation decimal;

    private final int[] positiveArguments = { 10, 50, 2500 };
    private final int[] negativeArguments = { -10, -50, -2500 };

    @BeforeAll
    void init() {
        decimal = new DecimalRepresentation();
    }

    @ParameterizedTest
    @ValueSource(strings = { "0" })
    void shouldReturnZeroIfValueNotSpecified(String zero) {
        assertEquals(zero, decimal.toBinary());
        assertEquals(zero, decimal.toOctal());
        assertEquals(zero, decimal.toHex());
    }

    @TestInstance(Lifecycle.PER_CLASS)
    @Nested
    @DisplayName("Decimal to Binary")
    class ToBinaryConversion {

        private Stream<Arguments> createPositiveBinaryArguments() {
            return Stream.of(Arguments.of(positiveArguments[0], "1010"),
                    Arguments.of(positiveArguments[1], "110010"),
                    Arguments.of(positiveArguments[2], "100111000100"));
        }

        private Stream<Arguments> createNegativeBinaryArguments() {
            return Stream.of(Arguments.of(negativeArguments[0], "11111111111111111111111111110110"),
                    Arguments.of(negativeArguments[1], "11111111111111111111111111001110"),
                    Arguments.of(negativeArguments[2], "11111111111111111111011000111100"));
        }

        @ParameterizedTest
        @MethodSource("createPositiveBinaryArguments")
        void shouldReturnExpectedValuesForPositiveNumbers(int positiveNumber, String bitRepresentation) {
            decimal.setNumber(positiveNumber);
            assertEquals(bitRepresentation, decimal.toBinary());
        }

        @ParameterizedTest
        @MethodSource("createNegativeBinaryArguments")
        void shouldReturnExpectedValuesForNegativeNumbers(int negativeNumber, String bitRepresentation) {
            decimal.setNumber(negativeNumber);
            assertEquals(bitRepresentation, decimal.toBinary());
        }
    }

    @TestInstance(Lifecycle.PER_CLASS)
    @Nested
    @DisplayName("Decimal to Octal")
    class ToOctalConversion {

        private Stream<Arguments> createPositiveOctalArguments() {
            return Stream.of(Arguments.of(positiveArguments[0], "12"),
                    Arguments.of(positiveArguments[1], "62"),
                    Arguments.of(positiveArguments[2], "4704"));
        }

        private Stream<Arguments> createNegativeOctalArguments() {
            return Stream.of(Arguments.of(negativeArguments[0], "37777777766"),
                    Arguments.of(negativeArguments[1], "37777777716"),
                    Arguments.of(negativeArguments[2], "37777773074"));
        }

        @ParameterizedTest
        @MethodSource("createPositiveOctalArguments")
        void shouldReturnExpectedValuesForPositiveNumbers(int positiveNumber, String bitRepresentation) {
            decimal.setNumber(positiveNumber);
            assertEquals(bitRepresentation, decimal.toOctal());
        }

        @ParameterizedTest
        @MethodSource("createNegativeOctalArguments")
        void shouldReturnExpectedValuesForNegativeNumbers(int negativeNumber, String bitRepresentation) {
            decimal.setNumber(negativeNumber);
            assertEquals(bitRepresentation, decimal.toOctal());
        }
    }

    @TestInstance(Lifecycle.PER_CLASS)
    @Nested
    @DisplayName("Decimal to Hexal")
    class ToHexalConversion {

        private Stream<Arguments> createPositiveHexArguments() {
            return Stream.of(Arguments.of(positiveArguments[0], "A"),
                    Arguments.of(positiveArguments[1], "32"),
                    Arguments.of(positiveArguments[2], "9C4"));
        }

        private Stream<Arguments> createNegativeHexArguments() {
            return Stream.of(Arguments.of(negativeArguments[0], "FFFFFFF6"),
                    Arguments.of(negativeArguments[1], "FFFFFFCE"),
                    Arguments.of(negativeArguments[2], "FFFFF63C"));
        }

        @ParameterizedTest
        @MethodSource("createPositiveHexArguments")
        void shouldReturnExpectedValuesForPositiveNumbers(int positiveNumber, String bitRepresentation) {
            decimal.setNumber(positiveNumber);
            assertEquals(bitRepresentation, decimal.toHex());
        }

        @ParameterizedTest
        @MethodSource("createNegativeHexArguments")
        void shouldReturnExpectedValuesForNegativeNumbers(int negativeNumber, String bitRepresentation) {
            decimal.setNumber(negativeNumber);
            assertEquals(bitRepresentation, decimal.toHex());
        }
    }
}

第三种方法:如果我要采用第二种方法,为每个测试用例创建新的参数方法几乎没有意义,所以我决定将 em 作为@CsvSource(因为只有常量注释参数,我必须在外部初始化参数数组):

@TestInstance(Lifecycle.PER_CLASS)
public class DecimalRepresentationTest {

    private DecimalRepresentation decimal;

    static private final String POSITIVE_A = "10", POSITIVE_B = "50", POSITIVE_C = "2500"; 
    static private final String NEGATIVE_A = "-10", NEGATIVE_B = "-50", NEGATIVE_C = "-2500";   

    @BeforeAll
    void init() {
        decimal = new DecimalRepresentation();
    }

    @ParameterizedTest
    @ValueSource(strings = "0")
    void shouldReturnZeroIfValueNotSpecified(String zero) {
        assertEquals(zero, decimal.toBinary());
        assertEquals(zero, decimal.toOctal());
        assertEquals(zero, decimal.toHex());
    }

    @TestInstance(Lifecycle.PER_CLASS)
    @Nested
    @DisplayName("Decimal to Binary")
    class ToBinaryConversion {

        @ParameterizedTest
        @CsvSource({"'" + POSITIVE_A + "', 1010",
                    "'" + POSITIVE_B + "', 110010",
                    "'" + POSITIVE_C + "', 100111000100"})
        void shouldReturnExpectedValuesForPositiveNumbers(int positiveNumber, String bitRepresentation) {
            decimal.setNumber(positiveNumber);
            assertEquals(bitRepresentation, decimal.toBinary());
        }

        @ParameterizedTest
        @CsvSource({"'" + NEGATIVE_A + "', 11111111111111111111111111110110",
                    "'" + NEGATIVE_B + "', 11111111111111111111111111001110",
                    "'" + NEGATIVE_C + "', 11111111111111111111011000111100"})
        void shouldReturnExpectedValuesForNegativeNumbers(int negativeNumber, String bitRepresentation) {
            decimal.setNumber(negativeNumber);
            assertEquals(bitRepresentation, decimal.toBinary());
        }
    }

    @TestInstance(Lifecycle.PER_CLASS)
    @Nested
    @DisplayName("Decimal to Octal")
    class ToOctalConversion {

        @ParameterizedTest
        @CsvSource({"'" + POSITIVE_A + "', 12",
                    "'" + POSITIVE_B + "', 62",
                    "'" + POSITIVE_C + "', 4704"})
        void shouldReturnExpectedValuesForPositiveNumbers(int positiveNumber, String bitRepresentation) {
            decimal.setNumber(positiveNumber);
            assertEquals(bitRepresentation, decimal.toOctal());
        }

        @ParameterizedTest
        @CsvSource({"'" + NEGATIVE_A + "', 37777777766",
                    "'" + NEGATIVE_B + "', 37777777716",
                    "'" + NEGATIVE_C + "', 37777773074"})
        void shouldReturnExpectedValuesForNegativeNumbers(int negativeNumber, String bitRepresentation) {
            decimal.setNumber(negativeNumber);
            assertEquals(bitRepresentation, decimal.toOctal());
        }
    }

    @TestInstance(Lifecycle.PER_CLASS)
    @Nested
    @DisplayName("Decimal to Hexal")
    class ToHexalConversion {

        @ParameterizedTest
        @CsvSource({"'" + POSITIVE_A + "', A",
                    "'" + POSITIVE_B + "', 32",
                    "'" + POSITIVE_C + "', 9C4"})
        void shouldReturnExpectedValuesForPositiveNumbers(int positiveNumber, String bitRepresentation) {
            decimal.setNumber(positiveNumber);
            assertEquals(bitRepresentation, decimal.toHex());
        }

        @ParameterizedTest
        @CsvSource({"'" + NEGATIVE_A + "', FFFFFFF6",
                    "'" + NEGATIVE_B + "', FFFFFFCE",
                    "'" + NEGATIVE_C + "', FFFFF63C"})
        void shouldReturnExpectedValuesForNegativeNumbers(int negativeNumber, String bitRepresentation) {
            decimal.setNumber(negativeNumber);
            assertEquals(bitRepresentation, decimal.toHex());
        }
    }
}

你怎么看?在这种情况下我应该使用哪一个?顺便说一句,当我测试我的自定义数字系统转换器之类的东西时,我是否可以完全信任标准 Java API,而不是硬编码预期结果,只需使用 Java' Integer.toString(int i, int radix) 方法?

【问题讨论】:

  • 可读性是需要考虑的一件事。我必须滚动多远才能解释您的代码。 fluent pattern 之类的方法是解决此问题的一种方法,您可以将方法链接添加到每个测试中。

标签: java unit-testing junit5


【解决方案1】:

我建议的一般准则是:

  • 让每个测试测试一个的东西。如果您在测试中有多个断言,那么如果第一个失败,您永远不知道后面的断言是否也失败 - 通过拥有多个断言,您隐藏了检测其他错误的机会。
  • 每个测试台都有自己的独立测试台,因此您不必依赖测试的顺序。当您还只想重新运行失败的测试时,这很有帮助。
  • 是的,您应该依靠标准的 Java 类来做他们所说的事情。为他们编写单元测试会比你需要做的工作更多(基本上,如果他们不按照他们说的去做,我们都会受到影响——所以他们会更好;他们也比你做的任何测试都经过了更彻底的测试可能会写)。
  • 保持测试简单明了(应用“KISS”原则,保持简单)

我知道这并不能直接解决您的代码布局问题,但也许它会帮助您做出一些决定。

【讨论】:

  • “断言一件事”不一定与“只有一个断言语句”的意思相同。您可以使用assertAll 来堆叠多个断言,但从概念的角度来看,您希望每次测试只断言一个概念,否则就更难推断出什么被破坏了。
猜你喜欢
  • 2020-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-20
  • 2016-11-29
  • 1970-01-01
  • 2018-10-01
  • 1970-01-01
相关资源
最近更新 更多