feat(tests): 增强测试框架的 SQL 日志记录与覆盖率报告

- 在 ApiCase 中添加 SQL 日志记录功能,失败时输出日志以便于调试
- 更新 TestApp 以支持测试模式下的 SQL 日志缓冲,成功与失败的测试均可记录日志
- 改进 PrintCoverage 方法,优化覆盖率报告的输出逻辑,支持部分运行的接口显示
- 增加 Note 备注功能,提升测试用例的可读性与文档化效果
- 更新相关文档,详细说明新功能的使用场景与示例
This commit is contained in:
2026-04-04 01:34:09 +08:00
parent b3f263c965
commit 17e1090077
8 changed files with 531 additions and 28 deletions
@@ -0,0 +1,220 @@
---
name: HoTime TDD Skill
overview: 创建一个 HoTime 框架的 TDD(测试驱动开发)Skill,指导 AI 按照"先写测试、再写实现、运行直到通过"的流程开发接口;同时在框架层面增加测试模式下的 SQL 日志智能控制,解决 AI 因日志过多无法继续执行的问题。
todos:
- id: sql-log-buffer
content: "db/db.go: 添加 testLogBuf *bytes.Buffer + testLogBufMu sync.Mutex 字段"
status: completed
- id: sql-log-query
content: "db/query.go: 修改 Query/Exec 两处 defer SQL 日志块,有 buffer 写 buffer、无 buffer 照旧"
status: completed
- id: sql-log-execute
content: "testing_api.go execute(): 请求前创建 buffer、请求后根据 pass/fail 决定 t.Log 或丢弃"
status: completed
- id: sql-log-init
content: "testing_helper.go NewTestApp(): Init 后设 Db.Mode=0 静默启动阶段日志,再恢复原 Mode 供运行时 buffer 使用"
status: completed
- id: create-skill
content: 创建 ~/.cursor/skills/hotime-tdd-testing/SKILL.mdTDD 工作流 + 测试编写范式 + API 速查 + 运行命令
status: completed
- id: verify-test
content: 运行单接口测试验证:通过时无 SQL 日志,失败时输出 SQL 日志
status: completed
isProject: false
---
# HoTime TDD API 测试 Skill 与 SQL 日志控制
## 一、SQL 日志智能控制(框架修改)-- 失败才打印
### 问题分析
SQL 日志由 `Db.Mode` 控制([db/query.go](d:/work/hotimev1.5/db/query.go) 第 40 行 `if that.Mode != 0`),`Log``*logrus.Logger`[db/db.go](d:/work/hotimev1.5/db/db.go) 第 22 行)。当前 config `"mode": 3`,每条 SQL 都实时打印。日志来源:
- 启动阶段 `Init()` -> `SetDB()` 中的 DB 初始化/表扫描
- 测试执行中每个接口的所有 SQL
- 批量运行时日志量指数级增长,导致 AI 输出被截断
### 方案:Buffer + Flush on Failure
**策略**:SQL 日志不直接打印,写入 per-case buffer。测试通过则丢弃,失败则通过 `t.Log()` 输出(Go 的 `t.Log` 只在失败或 `-v` 时显示)。
#### Step 1: db/db.go -- 添加 buffer 字段
```go
type HoTimeDB struct {
// ... 现有字段不变 ...
testLogBuf *bytes.Buffer // 测试模式:SQL 日志写入此 buffer 而非直接打印
testLogBufMu sync.Mutex // 保护 testLogBuf 并发写入
}
```
新增 helper 方法:
```go
func (that *HoTimeDB) SetTestLogBuffer(buf *bytes.Buffer) {
that.testLogBufMu.Lock()
that.testLogBuf = buf
that.testLogBufMu.Unlock()
}
func (that *HoTimeDB) FlushTestLog() string {
that.testLogBufMu.Lock()
defer that.testLogBufMu.Unlock()
if that.testLogBuf == nil {
return ""
}
s := that.testLogBuf.String()
that.testLogBuf.Reset()
return s
}
```
#### Step 2: db/query.go -- 修改两处 defer SQL 日志
`queryWithRetry` 第 39-45 行和 `execWithRetry` 第 117-123 行的 defer 块统一改为:
```go
defer func() {
if that.Mode != 0 {
that.mu.RLock()
msg := fmt.Sprintf("SQL:%s DATA:%v ERROR:%v", that.LastQuery, that.LastData, that.LastErr.GetError())
that.mu.RUnlock()
that.testLogBufMu.Lock()
if that.testLogBuf != nil {
that.testLogBuf.WriteString(msg + "\n")
} else {
that.Log.Info(msg)
}
that.testLogBufMu.Unlock()
}
}()
```
#### Step 3: testing_api.go execute() -- 管理 buffer 生命周期
`execute()` 函数的 `t.Run(desc, func(t *testing.T) { ... })` 内部:
```go
t.Run(desc, func(t *testing.T) {
// 启用 SQL 日志缓冲
buf := &bytes.Buffer{}
c.api.app.Db.SetTestLogBuffer(buf)
start := time.Now()
req := c.buildRequest(method)
w := httptest.NewRecorder()
c.api.app.Application.ServeHTTP(w, req)
duration := time.Since(start)
// ... 原有的断言逻辑 ...
// 停止缓冲
sqlLog := c.api.app.Db.FlushTestLog()
c.api.app.Db.SetTestLogBuffer(nil)
// 失败时输出 SQL 日志
if !passed && sqlLog != "" {
t.Logf("SQL 日志:\n%s", sqlLog)
}
// ... 原有的 record 收集逻辑 ...
})
```
#### Step 4: testing_helper.go NewTestApp() -- 静默启动阶段日志
`Init()` 内部调用 `SetDB()` 时 Mode 已从 config 读取(值为 3),会打印大量启动 SQL。在 `NewTestApp``Init()` 之后立即处理:
```go
func NewTestApp(configPath string, projects TestProj, ...) *TestApp {
app := Init(configPath)
// 启动阶段产生的 SQL 日志已经打印完毕,现在保存原始 Mode
// Mode 保持原值(非 0)让运行时 SQL 仍走 defer 日志逻辑,但会被 buffer 拦截
// 启动阶段的日志无法追溯拦截,但它们是一次性的固定输出,量可控
// ... 后续逻辑不变 ...
}
```
> 启动阶段的 SQL 日志(表扫描等)是 `Init()` 内部产生的,此时 buffer 尚未设置,会直接打印。这些是一次性的固定日志,量相对可控。如果仍然太多,可在 `Init()` 调用前临时设 `app.Db.Mode = 0`,但 `Init` 返回的 app 还没有 Db 对象——所以更实际的做法是在 `NewTestApp` 中 `Init()` 之后、`SetupForTest()` 之前设置 `Db.Mode = 0` 静默后续初始化 SQL,然后在 `SetupForTest()` 完成后恢复原始 Mode。
实际代码:
```go
func NewTestApp(configPath string, projects TestProj, ...) *TestApp {
app := Init(configPath)
// 保存原始 Mode,静默后续初始化阶段的 SQL 日志
origMode := app.Db.Mode
app.Db.Mode = 0
// ... listener / router / SetupForTest / DisableDbCache 等初始化 ...
// 恢复 Mode,运行时 SQL 日志由 testLogBuf 接管
app.Db.Mode = origMode
return &TestApp{ ... }
}
```
### 效果
- `go test ./app/... -v` -- 全量运行:通过的接口零 SQL 输出,失败的接口自动输出完整 SQL 链路
- `go test -run TestApi/app/order/create -v` -- 单接口:同上,通过无输出、失败有输出
- 任何粒度都自动适配,无需手动控制
### 涉及文件
- [db/db.go](d:/work/hotimev1.5/db/db.go) -- 添加 `testLogBuf` / `testLogBufMu` 字段 + helper 方法
- [db/query.go](d:/work/hotimev1.5/db/query.go) -- 修改 `queryWithRetry``execWithRetry` 的 defer 日志块
- [testing_api.go](d:/work/hotimev1.5/testing_api.go) -- `execute()` 中管理 buffer 生命周期
- [testing_helper.go](d:/work/hotimev1.5/testing_helper.go) -- `NewTestApp()` 中静默初始化阶段 + 恢复 Mode
---
## 二、TDD Skill 创建
### 存储位置
个人 Skill`~/.cursor/skills/hotime-tdd-testing/SKILL.md`(跨项目通用)
### Skill 核心内容
指导 AI 按以下 TDD 工作流开发 HoTime 接口:
**Phase 1: 编写测试用例(先于实现)**
-`xxx_test.go` 中定义 `CtrTest`,包含:
- 错误用例(权限/参数/业务校验,覆盖所有 status != 0 路径)
- 测试数据准备(`a.DB().Insert`
- 正确请求 + 响应结构校验(第三参数类型样本)
- Verify 统一校验(响应值断言 + 数据库状态校验)
**Phase 2: 实现接口**
- 在对应的 `.go` 文件中编写 handler
**Phase 3: 运行测试直到通过**
- 运行命令:`go test ./app/... -v -run TestApi/app/{ctr}/{method}`
- 修复失败用例,循环直到全部通过
**关键参考文档**
- Skill 内直接嵌入精简版的测试 API 速查表(链式 API、Verify 用法、DB 操作等)
- 详细文档指向 [Testing_API测试框架.md](d:/work/xbc/docs/Testing_API测试框架.md)
**SQL 日志行为(框架自动处理)**
- 任何粒度运行:通过的用例零 SQL 输出,失败的用例自动输出完整 SQL 链路
- 无需手动控制,框架通过 buffer 机制自动按 pass/fail 决定输出
### Skill 文件结构
```
~/.cursor/skills/hotime-tdd-testing/
SKILL.md -- 主文件(TDD 流程 + API 速查 + 运行指令)
```
直接在 SKILL.md 内嵌入精简的 API 参考(不分离文件),保持在 500 行以内,因为测试框架文档已经在项目 docs 目录中。
@@ -0,0 +1,69 @@
---
name: 补充 Note 方法示例
overview: 在 Testing_API测试框架.md 的"第二步:编写测试用例"部分补充 Note 方法的使用示例和说明,同步更新 SKILL.md。
todos:
- id: doc-step2-example
content: 文档:在第二步的完整订单示例中加入 Note 调用示范
status: completed
- id: doc-note-section
content: 文档:在用例编写范式中新增 Note 使用说明(典型场景 + 示例 + 何时必须用)
status: completed
- id: doc-hotimev15-sync
content: 同步修改 hotimev1.5 中的 Testing_API测试框架.md
status: completed
- id: skill-note-guide
content: SKILL.md:补充 Note 使用指引和更多场景示例
status: completed
isProject: false
---
# 补充 Note 方法示例到文档和 Skill
## 现状分析
目前 `Note` 方法在文档中的存在情况:
- [链式 API 参考](d:\work\xbc\docs\Testing_API测试框架.md) 第 474、494 行:仅出现在 API 表格中,一句话描述
- [API 速查表](d:\work\xbc\docs\Testing_API测试框架.md) 第 662-668 行:只有一个简短示例(`sign = MD5(...)`)
- [SKILL.md](C:\Users\92597.cursor\skills\hotime-tdd-testing\SKILL.md) 第 195-197 行:一行示例
**缺失**:"第二步:编写测试用例"(第 66-149 行)的完整订单示例中完全没有出现 `Note`,而这是新手学习的第一个入口。
## 需要修改的内容
### 1. 文档:第二步示例中加入 Note 使用
在 [Testing_API测试框架.md](d:\work\xbc\docs\Testing_API测试框架.md) 第 80-148 行的订单创建示例中,在**正确请求**部分加入 `Note` 调用,展示以下几种典型场景:
- 在正确请求处用 `Note` 说明参数值含义(如 `quantity=3` 是故意的用于校验总价 `29.9*3=89.7`
### 2. 文档:用例编写范式中新增 Note 独立小节
在"用例编写范式"部分(第 219 行之后),在"错误用例"之前或之后,新增一个关于 Note 的说明段落或小节,覆盖以下典型使用场景:
- **参数值参考**:说明某个参数值的来源或含义,如 `Note("status: 0=待付款, 1=已付款, 2=已发货, 3=已完成")`
- **算法说明**:简要描述接口涉及的签名/加密算法,如 `Note("sign = MD5(smsProxyKey+timestamp)")`
- **接口/用例说明**:补充 desc 无法涵盖的上下文,如 `Note("需先调用 /api/cart/add 加入购物车")``Note("此接口支持批量操作,最多100条")`
- 在用例编写范式表格中增加 Note 的定位说明
### 3. 文档:同步更新 hotimev1.5 中的同名文件
[hotimev1.5/docs/Testing_API测试框架.md](d:\work\hotimev1.5\docs\Testing_API测试框架.md) 需要做相同的修改。
### 4. Skill 文件:补充 Note 指引
在 [SKILL.md](C:\Users\92597.cursor\skills\hotime-tdd-testing\SKILL.md) 中:
- 在 Phase 1 的 1.3 小节之后(或 1.2 和 1.3 之间),新增 Note 的使用指引
- 说明何时应该使用 Note(有必要的场景),给出 2-3 个典型示例
- 在 API 速查的"备注"部分补充更多场景示例
## 修改要点
- Note 不改变测试逻辑,只是给调试控制台和 api-spec.json 添加可读说明
- Note 是可选的,但在以下场景**建议必须**使用:
- 参数中有枚举值(如 status、type)需要说明各值含义
- 涉及签名/加密等算法的接口
- 接口有前置依赖或特殊调用顺序
- Note 可以放在链式调用的任意位置(`a.Note(...)``c.Note(...)`