应为每条断言提供明确业务上下文,优先使用assertequals/asserttrue三参数重载或assertthat配合语义化匹配器,隔离非确定性因素,且每个业务分支需拆分为独立命名测试方法。

断言失败时只报 AssertionError,怎么快速定位是哪条业务规则没满足?
直接抛出带上下文的断言消息,而不是依赖默认异常堆栈。JUnit 5 的 assertThat 配合自定义描述最有效,但更轻量的做法是用 assertEquals 或 assertTrue 的三参数重载:
assertEquals("用户等级应为 VIP(因积分 ≥ 10000)",
"VIP",
user.getLevel());
这样失败时控制台第一行就显示业务含义,不用翻堆栈找调用点。避免写 assertTrue(user.getLevel().equals("VIP")) —— 错误信息只有 false,毫无上下文。
业务方法返回对象或集合,用 assertThat + Matchers 还是手写属性校验?
优先用 assertThat 配合 hasProperty 或 contains 等语义化匹配器,可读性高且失败提示清晰;但要注意:Spring Boot 2.2+ 默认不带 hamcrest,需显式引入 org.hamcrest:hamcrest 依赖。若项目已用 AssertJ,直接上 assertThat(result).hasFieldOrPropertyWithValue("status", "SUCCESS") 更稳——它对 null 安全,且支持链式深入校验嵌套字段。
不要手动遍历集合比对字段,容易漏判空集合或顺序敏感问题。例如验证订单列表含指定商品:
assertThat(orders)
.extracting("items")
.flatExtracting("name")
.contains("iPhone 15");
业务逻辑含时间、随机数或外部调用,断言总不稳定怎么办?
把非确定性因素隔离出去,用测试替身控制输入。常见做法:
- 时间相关:注入
Clock参数(Java 8+),测试中传入固定Clock.fixed(...) - 随机逻辑:将
Random抽成接口或构造函数参数,测试时传入种子固定的实例 - 外部服务:用
@MockBean(Spring)或Mockito.mock()拦截调用,预设返回值
切忌在测试里写 Thread.sleep(100) 等等待逻辑——这不是断言,是制造 flaky test。
一个业务方法有 5 种输入分支,要写 5 个独立测试还是塞进一个 @Test?
必须拆成 5 个命名清晰的独立测试方法。方法名直接体现场景,例如:shouldReturnDiscountedPrice_whenUserIsVipAndOrderOver500()。好处有三:
- 失败时明确知道哪个分支崩了,不用看断言位置猜逻辑
- 便于 IDE 单独运行某条路径,调试快
- 后续加新分支时,复制粘贴已有测试结构即可,不易漏覆盖
别用 @ParameterizedTest 把所有分支塞一起——当某条数据失败,你得手动查是第几个参数出问题,尤其当参数是复杂对象时,日志根本看不出对应哪条业务规则。
业务断言的核心不是“有没有 assert”,而是每条 assert 是否承载可读、可追溯、可独立执行的业务契约。越靠近领域语言,越容易被产品和 QA 看懂,也越难被随意绕过或注释掉。










