JUnit5中的assertAll与多个断言有何区别?

112

有没有理由将多个断言分组:

public void shouldTellIfPrime(){
    Assertions.assertAll(
            () -> assertTrue(isPrime(2)),
            () -> assertFalse(isPrime(4))
    );
}

不要这样做:

public void shouldTellIfPrime(){
    Assertions.assertTrue(isPrime(2));
    Assertions.assertFalse(isPrime(4));
}
3个回答

159

assertAll 的有趣之处在于,它会始终检查传递给它的所有断言,无论有多少个失败。如果全部通过,一切都好;如果至少有一个失败,则会得到所有错误(以及正确)的详细结果。

它最适合用于断言概念上归为一组的一组属性。例如,你的第一反应是:“我想把这个作为一个整体进行断言”。

示例

对于检查是否为质数和非质数,您的具体示例不是 assertAll 的最佳用例,因为它们互相独立 - 以至于我建议为此编写两个测试方法。

但是假设您有一个简单的类,例如带有字段 citystreetnumber 的地址,并且想要断言它们的值是否符合预期:

Address address = unitUnderTest.methodUnderTest();
assertEquals("Redwood Shores", address.getCity());
assertEquals("Oracle Parkway", address.getStreet());
assertEquals("500", address.getNumber());

现在,一旦第一个断言失败,您将永远看不到第二个断言的结果,这可能非常令人恼火。有许多方法可以解决这个问题,JUnit Jupiter的assertAll是其中之一:

Address address = unitUnderTest.methodUnderTest();
assertAll("Should return address of Oracle's headquarter",
    () -> assertEquals("Redwood Shores", address.getCity()),
    () -> assertEquals("Oracle Parkway", address.getStreet()),
    () -> assertEquals("500", address.getNumber())
);

如果被测试的方法返回错误的地址,您会收到以下错误:

org.opentest4j.MultipleFailuresError:
    Should return address of Oracle's headquarter (3 failures)
    expected: <Redwood Shores> but was: <Walldorf>
    expected: <Oracle Parkway> but was: <Dietmar-Hopp-Allee>
    expected: <500> but was: <16>

2
但不要滥用它!一个单独的测试方法应该总是只测试生产代码的一个假设。这就是为什么通常每个测试方法只有一个断言的主要原因。 - Timothy Truckle
4
我同意不滥用并仅测试一个假设,但我不同意计算断言的任何价值。这是一种纯语法考虑,没有任何相关性。以我的例子为例:很可能Address:equals测试了这些属性,因此我可以用一个断言来验证它们。从逻辑上讲,根本没有任何区别,但突然之间它就成了“只有一个assert”。如果我费力地为类创建一个Hamcrest匹配器,情况也是如此。 - Nicolai Parlog
但是我不同意计数断言有任何价值。我没有建议“计数”断言。每个测试方法一个断言是一个经验法则,不多也不少... 无论如何,如果您有多个断言,应该问自己是否您的测试确实测试了一个“单一假设”。 - Timothy Truckle
30
我不完全同意“一测一断言”的经验法则。它假设被测试的代码运行快且简单。随着测试从低级单位测试扩展到高级集成测试,这种假设不再成立。在结果上运行一个昂贵的代码一次,然后运行多个廉价的断言要比多次运行昂贵的代码并每次测试一件事情更加有效。在中间点上使用断言也可以帮助调试。只要你清晰地标记断言,多个断言使用起来没有问题。 - Fr Jeremy Krieg

11
根据文档

断言所有提供的可执行文件均不会抛出 AssertionError。

如果任何一个提供的可执行文件抛出 AssertionError,则所有剩余的可执行文件仍将被执行,并且所有失败将被聚合并报告为 MultipleFailuresError。但是,如果可执行文件抛出的异常不是 AssertionError,则执行将立即停止,并将该异常原样重新抛出,但掩盖为未经检查的异常。

因此,主要区别在于 `assertAll` 将允许所有断言执行而不中断流程,而像 `assertTrue` 等其他断言则会使用 `AssertionError` 停止测试运行。
在您的第一个示例中,两个断言都将执行,无论是否通过或失败;而在第二个示例中,如果第一个断言失败,则测试将停止。
关于问题“有没有理由将多个断言分组?”:
如果你想在单元测试中执行所有断言,那么就有理由对它们进行分组。

2

assertassertAll方法都是设计用来验证预期输出与实际输出是否相同的。

在简单的assert中,如果第一个断言失败,它将导致整个测试用例失败,并且不会验证其余的断言。而assertAll则会验证所有测试用例。

即使有一些断言失败,它也会继续执行其余的断言,并返回所有失败断言的验证结果。

例如:

public Apple addApple(int appleId, String appleName) {
    Apple apple = new Apple(appleId, appleName);
    return apple;
}

测试案例:

@Test
void addAppleAssertTest() {
    System.out.println("AppleCalculatorTest.addAppleTest");
    AppleCalculator appleCalculator = new AppleCalculator();
    Apple apple = appleCalculator.addApple(1, "apple");
    assertNotNull(apple, "apple object should not be null");
    assertEquals(11, apple.getAppleId(), "appleId should be 1");
    assertEquals("apple1", apple.getAppleName(), "appleName should be apple");
}
    
@Test
void addAppleAssertAllTest() {
    System.out.println("AppleCalculatorTest.addAppleTest");
    AppleCalculator appleCalculator = new AppleCalculator();
    Apple apple = appleCalculator.addApple(1, "apple");
    assertAll(() -> assertNotNull(apple, "apple object should not be null"),
        () -> assertEquals(11, apple.getAppleId(), "appleId should be 1"),
        () -> assertEquals("apple1", apple.getAppleName(), "appleName should be apple"));
}

1
你在现有的答案中添加了什么? - Ilya Serbis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接