# 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` 代替错误返回
