test-frontend/.claude/testing.md

147 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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