# 07 — 测试与错误处理规则 > **适用**:所有后端开发任务。AI 最容易忽略的两件事:写测试、统一错误码。 --- ## 测试规则 ### 必须写测试的场景 | 场景 | 测试类型 | 要求 | |------|----------|------| | 新增 Service 方法 | 单元测试 | 必须 | | 新增 Controller 接口 | 集成测试 | 必须 | | 涉及金额计算 | 单元测试 | 必须(多组边界值) | | 涉及状态流转 | 单元测试 | 必须(覆盖所有状态路径) | | 涉及外部接口调用 | 集成测试 | 必须(Mock 外部依赖) | | 修 Bug | 回归测试 | 必须(防止复现) | | 简单 CRUD | 可选 | 不强制 | ### AI 测试规则 **AI 完成任务后,必须同时交付测试代码。** 禁止说"功能写好了但测试你自己补"。 ```java // ✅ AI 必须产出完整测试 @SpringBootTest class TaskServiceTest { @Autowired private TaskService taskService; @Test void shouldCreateTask() { ... } @Test void shouldNotCreateTaskWithoutTitle() { ... } @Test void shouldCalculateRewardCorrectly() { ... } } // ❌ AI 不能这样 // "功能已完成,测试代码未编写,建议后续补充" ``` ### 测试命名 ``` 方法名_场景_预期结果 shouldCreateTask_whenValidInput shouldNotCreateTask_whenTitleIsEmpty shouldCalculateReward_whenDiscountApplied shouldReturnFail_whenTaskNotFound ``` ### 禁止 - 测试只测正常路径,不测异常路径 - 测试依赖执行顺序(每个测试必须独立) - 测试里有 `Thread.sleep()`(用 Awaitility) - 测试代码复制粘贴业务代码(测了个寂寞) --- ## 错误处理规则 ### 返回格式(不变式) ```java // ✅ 统一返回 Result.ok(data) // 成功 Result.error("原因") // 业务失败(用户可见) Result.error("系统错误") // 系统异常(用户不可见,只记录日志) // JSON 格式 { "code": 200, "message": "success", "data": {...} } // 成功 { "code": 500, "message": "任务不存在", "data": null } // 业务失败 { "code": 500, "message": "系统繁忙,请稍后重试", "data": null } // 系统异常 ``` ### 什么情况返回什么 ```java // 查不到 → 业务失败 if (task == null) return Result.error("任务不存在"); // 参数不合法 → 业务失败 if (page < 1) return Result.error("页码必须大于0"); // 没权限 → 业务失败 if (!hasPermission) return Result.error("无权操作"); // 数据库连接失败 → 系统异常(全局异常处理器统一捕获) // 第三方接口超时 → 系统异常(记录日志 + 返回通用错误) // 不需要手动 try-catch 每个地方,用全局异常处理器统一处理 ``` ### 全局异常处理器 ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusiness(BusinessException e) { log.warn("业务异常: {}", e.getMessage()); return Result.error(e.getMessage()); // 用户可见 } @ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error("系统异常", e); return Result.error("系统繁忙,请稍后重试"); // 用户不可见原文 } } ``` ### 禁止 | ❌ | ✅ | |----|-----| | `return Result.error(e.getMessage())` 把栈打印给用户 | `return Result.error("任务不存在")` | | 每个方法都 try-catch 一遍 | 全局异常处理器统一处理 | | 异常被吞掉 `catch(Exception e) {}` | 至少打日志 `log.error("xxx", e)` | | `return null` 让前端爆 NPE | `return Result.error("原因")` | | 成功和失败都用 code=200 | 失败用 code=500 | --- ## 检查表 ### 测试 - [ ] 新增的非 CRUD 方法有测试 - [ ] 测试覆盖了异常路径(不是只测正常路径) - [ ] 金额计算有边界值测试 - [ ] AI 交付包含测试代码 ### 错误处理 - [ ] 错误信息用户能看懂(不是技术术语) - [ ] 系统异常不暴露内部信息给用户 - [ ] 查不到/null 有明确 fail 返回 - [ ] 没有空 catch 块 - [ ] 没有 `return null` 代替错误返回