高可测试性异步函数需异常显式传播、按角色精准Mock、验证处理行为、严格隔离还原。须用throws声明异常,mock下游而非服务层,断言响应/日志/补偿动作,并每个测试新建mock、清理状态。

要让异步函数真正可测,关键不是“能不能跑通”,而是“异常路径是否清晰暴露、能否被精准触发和验证”。高可测试性的设计从代码结构开始,再配合恰当的 Mock 策略,才能覆盖超时、网络中断、校验失败、重试降级等真实场景。
一、让异常“露出来”:结构先行
如果异步函数内部把 SQLException 吞掉后只打日志,或者用 try-catch 包裹后返回 null,Mock 就无从介入。可测试的前提是异常必须显式传播:
- 用 throws 声明(Java)或 @Throws(Kotlin)标注可能抛出的检查型异常,如 IOException、SQLException
- 对非法参数主动 throw IllegalArgumentException 或自定义业务异常(如 UserNotFoundException),不静默 fallback
- 避免在 service 层 catch 底层异常后“吞掉再 return”,改用统一异常处理器(@ControllerAdvice)或包装后重新 throw
二、按异常角色精准 Mock
不同来源的异常,Mock 位置和方式完全不同:
- 输入校验异常:直接 mock 入口方法,例如 every { service.createUser(null) } throws IllegalArgumentException("name must not be null")
- 外部依赖异常:mock 真实调用链下游,比如 mock 数据库 mapper 的 selectById() 抛出 SQLException,而不是 mock 整个 service 类
- 多阶段异常:用 throwsMany()(MockK)或 thenThrow().thenReturn()(Mockito)模拟“第一次失败、第二次成功”,验证重试逻辑是否生效
三、不止断言“抛没抛”,更要验证“怎么处理”
测试异常的终点不是 assertThrows,而是确认系统行为是否符合预期:
- 检查 HTTP 响应状态码与错误体,例如 verify response.status == 400 && response.body.code == "VALIDATION_ERROR"
- 验证日志是否记录关键字段:verify(logger).error(eq("Failed to process order {}"), eq("ORD-789"))
- 确认补偿动作是否触发,如 MQ 消息是否发送、缓存是否清除——需 mock 对应消息模板或 cache client 并 verify 调用
- 若涉及私有状态(如 retryCount、fallbackUsed),用 ReflectionTestUtils 或 spy + doReturn() 辅助断言
四、隔离与还原:避免测试间污染
异步 Mock 容易因共享对象、静态字段或未清理的事件循环导致偶发失败:
- 每个测试用例都用 @BeforeEach 创建全新 mock 实例,不复用全局 static mock
- 修改了超时配置、重试次数等私有字段后,在 @AfterEach 中还原原始值,或使用 try-finally 清理
- Python 测试中坚持用 asyncio.run()(新建循环),避免在已有 loop 中重复 run_until_complete()
- Kotlin/Java 中慎用 PowerMock mock 静态方法;优先重构为可注入依赖,再 mock 接口实现

















