2. 动机

为什么需要软断言?先看个硬断言的例子:

@Test
void test_HardAssertions() {
    RequestMapper requestMapper = new RequestMapper();

    DomainModel result = requestMapper.map(new Request().setType("COMMON"));

    Assertions.assertThat(result.getId()).isNull();
    Assertions.assertThat(result.getType()).isEqualTo(1);
    Assertions.assertThat(result.getStatus()).isEqualTo("DRAFT");
}

这段代码测试一个 RequestDomainModel 的映射器。理想情况下,所有断言都通过才算测试通过。但如果映射器有 bug(比如 idstatus 都映射错误),运行测试会得到:

org.opentest4j.AssertionFailedError: 
expected: null
but was: "73a3f292-8131-4aa9-8d55-f0dba77adfdb"

踩坑点:测试只报告了第一个错误(id 不为 null),却没提 status 也错了。因为 AssertJ 默认使用硬断言——只要有一个断言失败,测试立即终止。

在真实项目中,如果映射对象有几十个字段,反复运行测试逐个修复错误会非常耗时。软断言正是为解决这个痛点而生。


3. AssertJ 中的软断言

软断言的核心逻辑:收集所有断言错误,最后统一报告。改写上面的测试:

@Test
void test_softAssertions() {
    RequestMapper requestMapper = new RequestMapper();

    DomainModel result = requestMapper.map(new Request().setType("COMMON"));

    SoftAssertions.assertSoftly(softAssertions -> {
        softAssertions.assertThat(result.getId()).isNull();
        softAssertions.assertThat(result.getType()).isEqualTo(1);
        softAssertions.assertThat(result.getStatus()).isEqualTo("DRAFT");
    });
}

现在所有断言都会执行,最终生成合并错误报告:

org.assertj.core.error.AssertJMultipleFailuresError: 
Multiple Failures (3 failures)
-- failure 1 --
expected: null
 but was: "66f8625c-b5e4-4705-9a49-94db3b347f72"
at SoftAssertionsUnitTest.lambda$test_softAssertions$0(SoftAssertionsUnitTest.java:19)
-- failure 2 --
expected: 1
 but was: 0
at SoftAssertionsUnitTest.lambda$test_softAssertions$0(SoftAssertionsUnitTest.java:20)
-- failure 3 --
expected: "DRAFT"
 but was: "NEW"
at SoftAssertionsUnitTest.lambda$test_softAssertions$0(SoftAssertionsUnitTest.java:21)

优势:一次运行暴露所有问题,大幅提升调试效率。

另一种写法

直接创建 SoftAssertions 实例:

@Test
void test_softAssertionsViaInstanceCreation() {
    RequestMapper requestMapper = new RequestMapper();

    DomainModel result = requestMapper.map(new Request().setType("COMMON"));

    SoftAssertions softAssertions = new SoftAssertions();
    softAssertions.assertThat(result.getId()).isNull();
    softAssertions.assertThat(result.getType()).isEqualTo(1);
    softAssertions.assertThat(result.getStatus()).isEqualTo("DRAFT");
    softAssertions.assertAll(); // 关键!触发错误收集
}

⚠️ 注意:必须调用 assertAll() 才会触发错误报告。两种写法功能完全等价,按喜好选择即可。


4. 其他测试框架中的软断言

软断言是测试框架的常见功能,只是命名可能不同:

  • JUnit 5:通过 assertAll() 实现(示例
  • TestNG:直接提供 SoftAssert 类(示例

核心思想一致:失败不中断,统一报告


5. 总结对比

硬断言 vs 软断言:

特性 硬断言 软断言
默认行为
主流框架支持
失败时立即终止测试

6. 结论

软断言通过延迟错误报告解决了硬断言的“一次只报一个错”问题,显著提升复杂测试的调试效率。虽然 AssertJ 的实现最知名,但 JUnit 5、TestNG 等框架也提供了类似功能。

代码示例见 GitHub 仓库


原始标题:Soft Assertions with AssertJ | Baeldung