java异常、日志与单元测试构成保障系统健壮性的三角支柱:异常定义错误响应,日志记录错误上下文,单元测试验证错误处理逻辑;三者需联动设计,通过自定义异常、结构化日志和全流程异常测试实现错误可捕获、可追溯、可验证。

Java 异常处理、日志与单元测试不是孤立环节,而是保障系统健壮性的三角支柱:异常负责“出错时怎么反应”,日志负责“出错时怎么记录”,单元测试则负责“出错逻辑是否真按预期工作”。三者联动,才能让错误可捕获、可追溯、可验证。
异常设计要为日志和测试留接口
抛出异常时,不能只写 throw new RuntimeException("错了")。这样既难打日志,也难在测试中精准断言。
- 自定义业务异常类,带错误码、上下文参数(如订单ID、用户ID),便于日志结构化输出和测试断言
- 避免空 catch 块;即使不处理,也要
logger.error("xxx失败", e),否则测试中无法验证日志是否触发 - 非必要不吞异常——吞掉的异常,单元测试就测不到,线上也就查不到
日志内容要支撑异常定位与测试验证
日志不是越多越好,而是要能回答三个问题:哪段代码出的错?错在什么输入?错成什么样?
- 在 catch 块中记录日志时,必须传入原始异常对象(
logger.error("查询用户失败", e)),否则堆栈丢失,测试也无法校验日志内容是否含关键字段 - 对关键业务异常(如支付失败、库存扣减失败),额外打一条结构化 warn 日志,含 errorCode、bizId、cause,方便监控告警和测试断言
- 单元测试中可用
LogCaptor(来自logback-test)捕获日志,验证是否在特定异常发生时输出了预期日志
单元测试要覆盖异常全流程
一个完整的异常测试用例,应同时验证:是否抛出正确类型异常、异常信息是否准确、日志是否记录、外部副作用(如数据库更新)是否未发生。
- 用
assertThrows()验证异常类型和消息:var ex = assertThrows<usernotfoundexception>(() -> service.getUser("missing"));</usernotfoundexception>assertTrue(ex.getMessage().contains("missing")); - 配合 Mockito 验证日志行为:注入
Logger的 mock,检查error()是否被调用且参数匹配 - 模拟不同异常分支:比如数据库超时(
SQLException)、网络不可达(IOException)、业务规则拒绝(InsufficientBalanceException),每种都单独写测试 - 负向测试同样重要:给本该抛异常的方法传合法参数,断言它 不抛异常;或给不该抛某种异常的地方强行抛出,断言它被正确转换或包装
实战整合示例:用户充值服务
假设有一个 RechargeService.recharge(String userId, BigDecimal amount) 方法:
- 金额 ≤ 0 → 抛
IllegalArgumentException,打 warn 日志 - 用户不存在 → 抛
UserNotFoundException,打 error 日志 - 余额更新失败(DB 异常)→ 包装为
RechargeFailedException,打 error + 原始异常
对应测试需包含:
– 正向:传负数金额,断言 IllegalArgumentException 且日志含 “非法金额”
– 正向:传未知用户 ID,断言 UserNotFoundException 且 error 日志触发
– 正向:DB 层抛 SQLException,断言最终抛出 RechargeFailedException 且 cause 是 SQLException
– 负向:传正常参数,断言无异常、账户余额正确增加、无 error 日志
异常、日志、测试三者闭环后,代码上线前就能暴露 80% 以上的容错缺陷,而不是等用户投诉才去翻日志猜原因。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











