写完一段逻辑,兴冲冲跑单元测试,结果红条一闪——测试失败。别慌,这在开发日常里太常见了。不是代码写错了,就是测试用例没覆盖全,又或者环境、数据、时序出了小岔子。
先别急着改代码,看清楚报错信息
打开终端或 IDE 的测试输出,重点盯三块内容:哪一行断言失败、期望值(expected)和实际值(actual)分别是啥、堆栈里最后调用的是你自己的哪个方法。比如:
Expected: 100
Actual : 99
at CalculatorTest.testAdd(CalculatorTest.java:23)这说明第23行的断言认为 add(49, 51) 应该等于100,但实际返回了99——很可能加法逻辑里少加了个1,或者用了错误的变量名。
复现它,再最小化它
在测试方法里临时加一句 System.out.println() 或打个断点,把输入参数、中间变量、返回值都打印出来。如果测试依赖外部服务或数据库,先注释掉相关调用,用简单返回值模拟(比如直接 return true),看是否还失败。能稳定复现,就成功了一半。
检查测试本身有没有问题
有时候不是代码错了,是测试写歪了。比如时间相关的测试:
@Test
void testOrderCreatedToday() {
Order order = new Order();
order.setCreatedAt(new Date()); // 这里可能精确到毫秒
assertTrue(order.isCreatedToday()); // isCreatedToday() 只比对年月日?
}如果 isCreatedToday() 内部是按日期字符串比较,而 new Date() 创建时刻刚好跨了午夜边界,就可能偶发失败。换成 LocalDate.now() 或固定日期构造更稳妥。
留意测试之间的干扰
多个测试共用静态变量、单例对象或共享文件路径,容易“串场”。比如 A 测试往全局缓存 put 了一个 key,B 测试读取时误用了这个残留值。解决办法很简单:每个测试运行前清空状态,或用 @BeforeEach 方法重置环境:
@BeforeEach
void setUp() {
Cache.clear();
Database.resetForTest();
}依赖外部系统?果断 Mock
调用了 HTTP 接口、数据库、消息队列?别让网络抖动或 DB 偶尔慢拖垮你的测试。用 Mockito 模拟依赖行为:
when(httpClient.get("/api/user/123")).thenReturn("{\"id\":123,\"name\":\"张三\"}");
User user = service.fetchUser(123);
assertEquals("张三", user.getName());这样既快又稳,还能专门测异常分支,比如模拟超时或 500 错误响应。
失败不是终点,是调试线索
有次我遇到一个定时任务测试总失败,查了半天发现是测试机本地时区设成了 UTC+5,而代码里硬编码了 "2024-06-01" 当作当天日期。改成 LocalDate.now(ZoneId.of("Asia/Shanghai")) 就稳了。单元测试失败,往往暴露的是代码里那些被忽略的隐含假设——时区、线程安全、空值处理、边界条件……把它当成一次轻量级代码审查,反而收获更大。