feat(api): 增强 API 测试框架功能与文档
- 在 Api 结构中新增 lastResp 字段以存储最近请求的响应 - 添加 Verify 方法,支持自定义校验函数并返回 ApiCase 构建器 - 新增 Resp 方法,获取最近一次请求的响应以便于断言 - 在 TestCollector 中添加 Visited 字段,记录已调用的路径 - 更新 GenerateSwagger 方法,支持部分运行时保留未运行端点的已有数据 - 完善文档,增加用例编写范式和示例,提升测试框架的可用性与易用性
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: Verify统一断言改造
|
||||
overview: 在 Api 上新增 Resp() 方法让 Verify 回调内可以同时做响应值断言和数据库校验,然后更新文档将统一的 Verify 模式作为推荐范式。
|
||||
todos:
|
||||
- id: add-resp-method
|
||||
content: "testing_api.go: Api 新增 lastResp 字段 + Resp() 方法,execute 中调 verifyFn 前设置 lastResp"
|
||||
status: completed
|
||||
- id: update-doc
|
||||
content: "docs/Testing_API测试框架.md: 将 Verify 统一断言作为推荐范式,resp 外部断言降为兼容用法"
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Verify 统一断言改造
|
||||
|
||||
## 问题
|
||||
|
||||
当前 `Verify(func(a *Api) error)` 的回调只能通过 `a.DB()` 做数据库校验,无法访问响应体。导致值断言必须在外部用 `resp.GetBody()` + `resp.Fail()` 完成,逻辑分散在两处。
|
||||
|
||||
## 方案:Api 上新增 Resp() 方法
|
||||
|
||||
在 `execute` 调用 `verifyFn` 之前,把 `resp` 存到 `Api` 上,然后通过 `a.Resp()` 暴露给回调。
|
||||
|
||||
### 代码改动([testing_api.go](testing_api.go))
|
||||
|
||||
**1. Api 结构体新增字段**(第 46-51 行附近):
|
||||
|
||||
```go
|
||||
type Api struct {
|
||||
app *TestApp
|
||||
path string
|
||||
t *testing.T
|
||||
session Map
|
||||
lastResp *ApiResponse // 新增:最近一次请求的响应
|
||||
}
|
||||
```
|
||||
|
||||
**2. 新增 Resp() 方法**:
|
||||
|
||||
```go
|
||||
// Resp 获取最近一次请求的响应(在 Verify 回调中使用)
|
||||
func (a *Api) Resp() *ApiResponse {
|
||||
return a.lastResp
|
||||
}
|
||||
```
|
||||
|
||||
**3. execute 中调用 verifyFn 前设置 lastResp**(第 296 行附近):
|
||||
|
||||
在 `if passed && c.verifyFn != nil` 之前加一行:
|
||||
|
||||
```go
|
||||
c.api.lastResp = resp
|
||||
```
|
||||
|
||||
**4. WithSession 中传递 lastResp**(可选,因为 WithSession 创建新实例时 lastResp 默认 nil 即可,Verify 内不会跨实例调用)。
|
||||
|
||||
### 改动影响
|
||||
|
||||
- Verify 的签名 `func(a *Api) error` 不变,完全向后兼容
|
||||
- 现有 Verify 回调代码无需修改
|
||||
- 新的 Verify 回调可以用 `a.Resp()` 访问响应体做值断言
|
||||
- 外部 `resp.GetBody()` + `resp.Fail()` 仍然可用,作为兼容方式保留
|
||||
|
||||
### 文档改动([docs/Testing_API测试框架.md](docs/Testing_API测试框架.md))
|
||||
|
||||
将"用例编写范式"中的推荐模式从"Verify 做 DB 校验 + 外部 resp 做值断言"改为**统一在 Verify 内完成**:
|
||||
|
||||
```go
|
||||
resp := a.WithSession(Map{"user_id": int64(1)}).
|
||||
JSON(Map{"goods_id": goodsId, "quantity": 3, "address": "XX路1号"}).
|
||||
Verify(func(a *Api) error {
|
||||
// 响应值断言
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result.GetCeilInt64("id") <= 0 {
|
||||
return fmt.Errorf("订单 ID 必须大于 0")
|
||||
}
|
||||
if result.GetString("sn") == "" {
|
||||
return fmt.Errorf("订单编号 sn 不能为空")
|
||||
}
|
||||
|
||||
// 数据库状态校验
|
||||
row := a.DB().Get("order", "*", Map{"AND": Map{
|
||||
"user_id": int64(1), "goods_id": goodsId,
|
||||
}})
|
||||
if row == nil {
|
||||
return fmt.Errorf("order 表未写入订单记录")
|
||||
}
|
||||
if row.GetCeilInt64("state") != 0 {
|
||||
return fmt.Errorf("新订单 state 期望 0, 实际 %d", row.GetCeilInt64("state"))
|
||||
}
|
||||
// ...
|
||||
return nil
|
||||
}).
|
||||
Post("正常创建订单", 0, Map{
|
||||
"id": int64(1), "sn": "sample", "total_price": float64(1.0),
|
||||
})
|
||||
```
|
||||
|
||||
文档中需要调整的位置:
|
||||
|
||||
- 快速开始的完整范例
|
||||
- 用例编写范式的流程图和表格
|
||||
- "正确请求与响应断言"小节(合并为"正确请求与 Verify 校验")
|
||||
- "不好的写法 vs 推荐的写法"对比
|
||||
- 保留 `resp.GetBody()` + `resp.Fail()` 的说明但标注为兼容用法
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: 整理推荐写法风格
|
||||
overview: 将全文散落的"推荐/不推荐"对比块统一清理,文档全程只陈述正确做法,在「用例编写范式」末尾新增「简写支持说明」小节,介绍框架支持的简写行为,并以一句忠告收尾。
|
||||
todos:
|
||||
- id: clean-error-case-block
|
||||
content: 错误用例代码块:去掉 `// 推荐:` 前缀和整个「不推荐」代码段(第 246-256 行)
|
||||
status: completed
|
||||
- id: replace-bad-vs-good
|
||||
content: 「不好的写法 vs 推荐的写法」整节替换为「简写支持说明」,说明框架支持的简写行为+场景,以忠告句收尾(第 412-458 行)
|
||||
status: completed
|
||||
- id: clean-apiresponse-labels
|
||||
content: ApiResponse 去掉「推荐方式」标签,「兼容方式」改为自然描述(第 595-613 行)
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 整理推荐写法风格
|
||||
|
||||
目标文件:[docs/Testing_API测试框架.md](docs/Testing_API测试框架.md)
|
||||
|
||||
## 三处改动
|
||||
|
||||
### 1. 第 246-256 行 — 错误用例编写规范代码块
|
||||
|
||||
当前:代码块里有 `// 推荐:...` 和 `// 不推荐:...` 两段
|
||||
|
||||
改后:只保留完整写法的代码,去掉 `// 推荐:` 注释前缀和整个"不推荐"代码段。
|
||||
|
||||
```go
|
||||
a.Post("未登录访问", 2, "请先登录")
|
||||
a.JSON(Map{"quantity": 3}).Post("缺少商品ID", 3, "请选择商品")
|
||||
a.JSON(Map{"goods_id": int64(1)}).Post("缺少数量", 3, "请填写购买数量")
|
||||
a.JSON(Map{"goods_id": int64(999)}).Post("商品不存在", 4, "商品不存在")
|
||||
```
|
||||
|
||||
### 2. 第 412-458 行 — 「不好的写法 vs 推荐的写法」整节
|
||||
|
||||
当前:三组 bad/good 对比块(省略 msg、只校验 status、硬编码 ID)
|
||||
|
||||
改后:整节替换为「简写支持说明」,结构如下:
|
||||
|
||||
- **标题**:`### 简写支持说明`
|
||||
- **说明框架支持的三种简写**,每种说明行为(不是批评,是告知机制):
|
||||
- 省略第三参数:`a.Post("请先登录", 2)` — desc 自动作为期望 msg,适合 desc 与 msg 完全一致的场景
|
||||
- 省略 expect:`a.Post("创建成功", 0)` — 仅断言 status=0,不校验 result 内容或数据库状态
|
||||
- 省略 Verify:适合纯查询、无副作用的接口,只需验证格式即可
|
||||
- **忠告**(一行):`> 对于涉及数据写入、状态变更、副作用的接口,省略过程校验等于充分不测试。`
|
||||
|
||||
### 3. 第 595-613 行 — ApiResponse「推荐方式 / 兼容方式」标签
|
||||
|
||||
当前:用「推荐方式」和「兼容方式」两个标签
|
||||
|
||||
改后:去掉「推荐方式 —」标签,直接展示 Verify 用法;「兼容方式」改为:`也可以通过返回值在外部断言(适合不需要 DB 校验的简单场景):`
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: 精简测试框架文档
|
||||
overview: 将 Testing_API测试框架.md 从 1210 行精简到约 750-800 行,删除与"编写测试用例"无关的冗余内容,同时保留/强化全过程验证示例,防止 AI 写出只验证接口通不通的浅层测试。
|
||||
todos:
|
||||
- id: simplify-biz-code
|
||||
content: 「快速开始 → 第一步:业务代码」改成极简示例(3-5行的伪代码),不再展开完整 OrderCtr,节约约40行
|
||||
status: completed
|
||||
- id: delete-framework-changes
|
||||
content: 删除「框架改动说明」整节(约25行)
|
||||
status: completed
|
||||
- id: delete-login-compare
|
||||
content: 删除「不好的写法 vs 推荐的写法」中的登录完整对比示例(约45行),前面各小节已有对比,不需要再重复一遍
|
||||
status: completed
|
||||
- id: trim-swagger-console
|
||||
content: 缩减「API 调试控制台」,只保留 GenerateSwagger 调用方式和输出目录结构,删除14行功能特性表和侧边栏示意图(约-30行)
|
||||
status: completed
|
||||
- id: trim-api-spec-fields
|
||||
content: 删除「api-spec.json 覆盖率字段说明」中的字段详解大表和徽章规则(约-30行),只保留 JSON 结构示例
|
||||
status: completed
|
||||
- id: trim-concurrency
|
||||
content: 精简「并发保护与缓存隔离」,删除逐操作加锁保护表,只保留结论性描述(约-20行)
|
||||
status: completed
|
||||
- id: trim-apiresponse
|
||||
content: 删除 ApiResponse GetBody vs Obj 的解释性注释块(约-10行)
|
||||
status: completed
|
||||
- id: trim-run-range
|
||||
content: 删除「指定运行范围」末尾重复的子测试层级树(约-10行)
|
||||
status: completed
|
||||
- id: trim-binary
|
||||
content: 精简「二进制与非JSON响应校验」,删除图片下载示例(与xlsx示例高度重复,约-30行)
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 精简测试框架文档
|
||||
|
||||
目标:[docs/Testing_API测试框架.md](docs/Testing_API测试框架.md) 从 1210 行 → 约 750-800 行
|
||||
|
||||
## 核心原则调整(与初稿的区别)
|
||||
|
||||
- **快速开始必须完整**:第一步「业务代码」不删除,改为极简示意(3-5行伪代码),让读者明白"这里有个接口"即可,不展开完整逻辑
|
||||
- **全过程验证示例必须保留并强调**:Verify + DB 校验 + 响应值断言的示例是文档核心,AI 容易偷懒只写 `Post("xxx", 0)` 不做任何校验,这种浅层用例要明确标为"不好的写法"
|
||||
- **其余按实用性裁剪**
|
||||
|
||||
## 删除/精简内容(约 -400 行)
|
||||
|
||||
- **精简「第一步:业务代码」**(-40 行):OrderCtr 有50行,改成极简伪代码 + 注释说明接口做什么,让后续测试示例有上下文即可
|
||||
- **删除「框架改动说明」整节**(-25 行):内部实现历史,对写测试无任何指导价值
|
||||
- **删除「不好的写法 → 登录完整对比」**(-45 行):前面3个对比子节已充分说明问题,第4个只是换了场景重复
|
||||
- **缩减「API 调试控制台」**(-30 行):只保留 `GenerateSwagger` 调用 + 输出目录结构,删除14行功能表和侧边栏结构图
|
||||
- **删除「api-spec.json 字段详解表」**(-30 行):与写测试无关
|
||||
- **精简「并发保护与缓存隔离」**(-20 行):删除逐操作加锁表,保留结论
|
||||
- **删除 ApiResponse GetBody vs Obj 解释块**(-10 行)
|
||||
- **删除「指定运行范围」末尾层级树**(-10 行):bash 示例中注释已足够
|
||||
- **精简「二进制响应校验」**(-30 行):只保留 xlsx + CSV,删除重复的图片示例
|
||||
|
||||
## 保留/强化内容
|
||||
|
||||
- 概述能力表格
|
||||
- **快速开始(完整4步,业务代码改极简)**
|
||||
- **用例编写范式(全部4节 + 不好vs推荐对比的前3节)** ← 核心,不裁
|
||||
- 核心类型
|
||||
- 链式 API 参考(三张表)
|
||||
- API 速查表
|
||||
- 事务隔离机制
|
||||
- 覆盖率报告(保留 PrintCoverage 输出示例)
|
||||
- 指定运行范围(bash 示例)
|
||||
- 并发保护与缓存隔离(精简版)
|
||||
- 二进制响应校验(精简版)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
name: 重写API测试框架文档
|
||||
overview: 重新编撰 Testing_API测试框架.md,以"错误用例先行 → 准备数据 → 正确请求(含响应断言+DB校验)"的完整流程为核心范式,让文档成为可直接照搬的编写指南。
|
||||
todos:
|
||||
- id: rewrite-doc
|
||||
content: 重写 docs/Testing_API测试框架.md:错误用例先行 → 模拟数据准备 → 正确请求(响应断言必须+DB校验) → 逐步讲解各环节 → API速查表 → 其余章节微调
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 重写 API 测试框架文档
|
||||
|
||||
## 用户确认的编写规范
|
||||
|
||||
1. **顺序**:先写错误请求用例,再写正确请求用例
|
||||
2. **错误用例**:三个参数必须填满 `Post("描述", status, "具体错误消息")`,因为错误码是复用的(如 status=3 可能对应多种参数校验错误)
|
||||
3. **正确请求前**:用 `a.DB()` 模拟插入数据,既准备测试依赖数据,也可作为后续 DB 校验的对照
|
||||
4. **响应字段断言**:正确响应情况下**必须**断言(至少覆盖重要字段),不能只写 `Post("描述", 0)`
|
||||
5. **数据库校验**:只要接口改变了数据库状态,都**建议**用 Verify 校验结果
|
||||
|
||||
## 标准用例编写流程(范式)
|
||||
|
||||
```
|
||||
错误用例(参数校验、权限校验等)
|
||||
↓
|
||||
准备测试数据(a.DB().Insert/Update 模拟前置数据)
|
||||
↓
|
||||
正确请求 + 响应结构校验(ExpectResult / 第三参数 Map)
|
||||
↓
|
||||
响应字段断言(resp.GetBody() 检查关键业务字段)
|
||||
↓
|
||||
数据库状态校验(Verify 查库确认写入/更新结果)
|
||||
```
|
||||
|
||||
## 核心范例(贯穿文档的"创建订单"示例)
|
||||
|
||||
以下为快速开始中使用的完整范例,展示从业务代码到测试用例的映射:
|
||||
|
||||
```go
|
||||
// order_test.go
|
||||
var OrderTest = CtrTest{
|
||||
"create": {Desc: "创建订单", Func: func(a *Api) {
|
||||
// ======== 第一步:错误用例 ========
|
||||
// 错误码复用,三个参数都必须填满
|
||||
a.JSON(Map{"goods_id": int64(1), "quantity": 3, "address": "XX路1号"}).
|
||||
Post("未登录创建订单", 2, "请先登录")
|
||||
a.WithSession(Map{"user_id": int64(1)}).
|
||||
JSON(Map{"quantity": 3, "address": "XX路1号"}).
|
||||
Post("缺少商品ID", 3, "请选择商品")
|
||||
a.WithSession(Map{"user_id": int64(1)}).
|
||||
JSON(Map{"goods_id": int64(1), "address": "XX路1号"}).
|
||||
Post("缺少数量", 3, "请填写购买数量")
|
||||
a.WithSession(Map{"user_id": int64(1)}).
|
||||
JSON(Map{"goods_id": int64(999), "quantity": 3, "address": "XX路1号"}).
|
||||
Post("商品不存在", 4, "商品不存在")
|
||||
|
||||
// ======== 第二步:准备测试数据 ========
|
||||
// 插入一条商品数据,后面的正确请求会依赖它
|
||||
goodsId := a.DB().Insert("goods", Map{
|
||||
"name": "测试商品", "price": 29.9, "stock": 100, "state": 1,
|
||||
})
|
||||
|
||||
// ======== 第三步:正确请求 + 响应断言 + DB校验 ========
|
||||
resp := a.WithSession(Map{"user_id": int64(1)}).
|
||||
JSON(Map{"goods_id": goodsId, "quantity": 3, "address": "XX路1号"}).
|
||||
Verify(func(a *Api) error {
|
||||
row := a.DB().Get("order", "*", Map{"AND": Map{
|
||||
"user_id": int64(1), "goods_id": goodsId,
|
||||
}})
|
||||
if row == nil {
|
||||
return fmt.Errorf("order 表未写入订单记录")
|
||||
}
|
||||
if row.GetCeilInt64("state") != 0 {
|
||||
return fmt.Errorf("新订单 state 期望 0, 实际 %d", row.GetCeilInt64("state"))
|
||||
}
|
||||
if row.GetCeilFloat64("total_price") != 29.9*3 {
|
||||
return fmt.Errorf("total_price 期望 %.2f, 实际 %.2f", 29.9*3, row.GetCeilFloat64("total_price"))
|
||||
}
|
||||
// 校验库存扣减
|
||||
goods := a.DB().Get("goods", "stock", Map{"id": goodsId})
|
||||
if goods.GetCeilInt64("stock") != 97 {
|
||||
return fmt.Errorf("库存期望 97, 实际 %d", goods.GetCeilInt64("stock"))
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Post("正常创建订单", 0, Map{
|
||||
"id": int64(1), "sn": "sample", "total_price": float64(1.0),
|
||||
})
|
||||
|
||||
// 关键业务字段断言
|
||||
orderId := resp.GetBody().GetMap("result").GetCeilInt64("id")
|
||||
if orderId <= 0 {
|
||||
resp.Fail("返回的订单 ID 必须大于 0")
|
||||
}
|
||||
sn := resp.GetBody().GetMap("result").GetString("sn")
|
||||
if sn == "" {
|
||||
resp.Fail("返回的订单编号 sn 不能为空")
|
||||
}
|
||||
}},
|
||||
}
|
||||
```
|
||||
|
||||
## 文档结构
|
||||
|
||||
### 不变章节(微调措辞)
|
||||
|
||||
- 概述(能力表格)
|
||||
- 核心类型
|
||||
- 链式 API 参考(在 expect 参数规则部分强调:错误用例建议三参数写满)
|
||||
- 事务隔离机制
|
||||
- 覆盖率报告
|
||||
- API 调试控制台
|
||||
- 指定运行范围
|
||||
- 并发保护与缓存隔离
|
||||
- 框架改动说明
|
||||
|
||||
### 重写/新增章节
|
||||
|
||||
#### 1. "快速开始" — 使用上述完整范例
|
||||
|
||||
- 先展示业务代码(handler),再展示测试代码
|
||||
- 范例中完整体现五步流程
|
||||
- 目录结构、注册方式、运行方式保持原有说明
|
||||
|
||||
#### 2. "用例编写范式" — 新增章节,替代原"完整场景对照"
|
||||
|
||||
按顺序逐步讲解范例中的每个环节:
|
||||
|
||||
- **错误用例编写规范**:为什么三参数必须写满(错误码复用)、如何覆盖权限/参数/业务错误
|
||||
- **测试数据准备**:`a.DB().Insert()` 的使用场景和注意事项(事务内操作,自动回滚)
|
||||
- **正确请求 + 响应结构校验**:第三参数 Map 的类型样本规则、ExpectResult 后置校验
|
||||
- **响应字段断言**:`resp.GetBody()` / `resp.Fail()` 的用法、哪些字段必须断言
|
||||
- **数据库状态校验**:Verify 的使用时机(只要改了DB就建议校验)、校验主表+关联表+字段值
|
||||
- 包含"不好的写法 vs 推荐的写法"对比
|
||||
|
||||
#### 3. "API 速查表" — 精简版场景对照
|
||||
|
||||
保留各种参数组合(JSON/Form/Query/File/混合/Session 等)的快速参考,但每种只一行示例代码,不再是大段散乱的代码块。
|
||||
|
||||
## 改动范围
|
||||
|
||||
- 仅改动 `docs/Testing_API测试框架.md`,约 750-850 行
|
||||
- 不涉及 Go 源码
|
||||
- 不涉及其他文档
|
||||
|
||||
Reference in New Issue
Block a user