Files
hotime/.cursor/plans/verify统一断言改造_2510a4b9.plan.md
T
hoteas 37a67f5810 feat(api): 增强 API 测试框架功能与文档
- 在 Api 结构中新增 lastResp 字段以存储最近请求的响应
- 添加 Verify 方法,支持自定义校验函数并返回 ApiCase 构建器
- 新增 Resp 方法,获取最近一次请求的响应以便于断言
- 在 TestCollector 中添加 Visited 字段,记录已调用的路径
- 更新 GenerateSwagger 方法,支持部分运行时保留未运行端点的已有数据
- 完善文档,增加用例编写范式和示例,提升测试框架的可用性与易用性
2026-03-30 01:55:07 +08:00

3.6 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
Verify统一断言改造 在 Api 上新增 Resp() 方法让 Verify 回调内可以同时做响应值断言和数据库校验,然后更新文档将统一的 Verify 模式作为推荐范式。
id content status
add-resp-method testing_api.go: Api 新增 lastResp 字段 + Resp() 方法,execute 中调 verifyFn 前设置 lastResp completed
id content status
update-doc docs/Testing_API测试框架.md: 将 Verify 统一断言作为推荐范式,resp 外部断言降为兼容用法 completed
false

Verify 统一断言改造

问题

当前 Verify(func(a *Api) error) 的回调只能通过 a.DB() 做数据库校验,无法访问响应体。导致值断言必须在外部用 resp.GetBody() + resp.Fail() 完成,逻辑分散在两处。

方案:Api 上新增 Resp() 方法

execute 调用 verifyFn 之前,把 resp 存到 Api 上,然后通过 a.Resp() 暴露给回调。

代码改动(testing_api.go

1. Api 结构体新增字段(第 46-51 行附近):

type Api struct {
    app      *TestApp
    path     string
    t        *testing.T
    session  Map
    lastResp *ApiResponse  // 新增:最近一次请求的响应
}

2. 新增 Resp() 方法

// Resp 获取最近一次请求的响应(在 Verify 回调中使用)
func (a *Api) Resp() *ApiResponse {
    return a.lastResp
}

3. execute 中调用 verifyFn 前设置 lastResp(第 296 行附近):

if passed && c.verifyFn != nil 之前加一行:

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

将"用例编写范式"中的推荐模式从"Verify 做 DB 校验 + 外部 resp 做值断言"改为统一在 Verify 内完成

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() 的说明但标注为兼容用法