Files
hotime/.cursor/plans/hotime_tdd_skill_a252ff6f.plan.md
T
hoteas 6b689f3a1b chore(logging): 更新日志重定向与捕获功能
- 在 .gitignore 中添加调试日志文件的忽略规则,避免不必要的调试信息被提交
- 修改 application.go 中的 stdout 重定向逻辑,使用 log.CaptureStream 以支持更灵活的日志捕获
- 更新 README 文档,增加对日志重定向功能的说明
2026-07-13 07:45:51 +08:00

7.9 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
HoTime TDD Skill 创建一个 HoTime 框架的 TDD(测试驱动开发)Skill,指导 AI 按照"先写测试、再写实现、运行直到通过"的流程开发接口;同时在框架层面增加测试模式下的 SQL 日志智能控制,解决 AI 因日志过多无法继续执行的问题。
id content status
sql-log-buffer db/db.go: 添加 testLogBuf *bytes.Buffer + testLogBufMu sync.Mutex 字段 completed
id content status
sql-log-query db/query.go: 修改 Query/Exec 两处 defer SQL 日志块,有 buffer 写 buffer、无 buffer 照旧 completed
id content status
sql-log-execute testing_api.go execute(): 请求前创建 buffer、请求后根据 pass/fail 决定 t.Log 或丢弃 completed
id content status
sql-log-init testing_helper.go NewTestApp(): Init 后设 Db.Mode=0 静默启动阶段日志,再恢复原 Mode 供运行时 buffer 使用 completed
id content status
create-skill 创建 ~/.cursor/skills/hotime-tdd-testing/SKILL.mdTDD 工作流 + 测试编写范式 + API 速查 + 运行命令 completed
id content status
verify-test 运行单接口测试验证:通过时无 SQL 日志,失败时输出 SQL 日志 completed
false

HoTime TDD API 测试 Skill 与 SQL 日志控制

一、SQL 日志智能控制(框架修改)-- 失败才打印

问题分析

SQL 日志由 Db.Mode 控制(db/query.go 第 40 行 if that.Mode != 0),Log*logrus.Loggerdb/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 字段

type HoTimeDB struct {
    // ... 现有字段不变 ...
    testLogBuf   *bytes.Buffer  // 测试模式:SQL 日志写入此 buffer 而非直接打印
    testLogBufMu sync.Mutex     // 保护 testLogBuf 并发写入
}

新增 helper 方法:

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 块统一改为:

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) { ... }) 内部:

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。在 NewTestAppInit() 之后立即处理:

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 对象——所以更实际的做法是在 NewTestAppInit() 之后、SetupForTest() 之前设置 Db.Mode = 0 静默后续初始化 SQL,然后在 SetupForTest() 完成后恢复原始 Mode。

实际代码:

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 -- 添加 testLogBuf / testLogBufMu 字段 + helper 方法
  • db/query.go -- 修改 queryWithRetryexecWithRetry 的 defer 日志块
  • testing_api.go -- execute() 中管理 buffer 生命周期
  • 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

SQL 日志行为(框架自动处理)

  • 任何粒度运行:通过的用例零 SQL 输出,失败的用例自动输出完整 SQL 链路
  • 无需手动控制,框架通过 buffer 机制自动按 pass/fail 决定输出

Skill 文件结构

~/.cursor/skills/hotime-tdd-testing/
  SKILL.md          -- 主文件(TDD 流程 + API 速查 + 运行指令)

直接在 SKILL.md 内嵌入精简的 API 参考(不分离文件),保持在 500 行以内,因为测试框架文档已经在项目 docs 目录中。