--- 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.md:TDD 工作流 + 测试编写范式 + 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/your-app/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 目录中。