--- name: API测试框架完整实现 overview: 在 hotimev1.5 框架中实现完整的 API 测试基础设施:链式测试 API、事务回滚隔离、覆盖率追踪、Swagger 文档自动生成。 todos: - id: db-testtx content: db/db.go:加 testTx 字段 + BeginTestTx/RollbackTestTx 方法 status: completed - id: db-query content: db/query.go:Query(行64) 和 Exec(行120) 加 testTx 优先判断 status: completed - id: db-transaction content: db/transaction.go:Action 加 testTx 传递 + SAVEPOINT 分支 status: completed - id: testing-helper content: 新增 testing_helper.go:TestApp、NewTestApp、SetupForTest、RunTests(事务隔离)、PrintCoverage status: completed - id: testing-api content: 新增 testing_api.go:注册类型 + Api/ApiCase/ApiResponse 链式 API + TestCollector status: completed - id: testing-swagger content: 新增 testing_swagger.go:GenerateSwagger 合并所有项目生成 Swagger status: completed - id: xbc-integration content: xbc 业务层:user.go 加 UserTest、init.go 加 ProjectTest、新增 app_test.go status: completed isProject: false --- # API 测试框架完整实现 ## 一、事务回滚隔离(db 层改造) 核心思路:给 `HoTimeDB` 增加 `testTx` 字段,优先级 `testTx > Tx > DB`。测试模式下 `Action()` 用 MySQL `SAVEPOINT` 代替真事务,保证嵌套事务都在外层测试事务内。 ### 1. [db/db.go](d:/work/hotimev1.5/db/db.go) -- 加字段 + 方法 ```go // HoTimeDB 结构体新增字段 testTx *sql.Tx // 测试事务:设置后所有操作都在此事务内 // 新增两个方法 func (that *HoTimeDB) BeginTestTx() error func (that *HoTimeDB) RollbackTestTx() error ``` ### 2. [db/query.go](d:/work/hotimev1.5/db/query.go) -- Query(行64) 和 Exec(行120) 各加一个判断 ```go // 原:if that.Tx != nil { ... } else { ... } // 改: if that.testTx != nil { resl, err = that.testTx.Query(query, processedArgs...) } else if that.Tx != nil { resl, err = that.Tx.Query(query, processedArgs...) } else { resl, err = db.Query(query, processedArgs...) } // Exec 同理 ``` ### 3. [db/transaction.go](d:/work/hotimev1.5/db/transaction.go) -- Action 加 SAVEPOINT 分支 ```go func (that *HoTimeDB) Action(action func(db HoTimeDB) (isSuccess bool)) (isSuccess bool) { db := HoTimeDB{ // ...现有字段拷贝... testTx: that.testTx, // 新增:传递 testTx } // 新增:测试模式用 SAVEPOINT if that.testTx != nil { spName := "sp_" + Md5(ObjToStr(RandX(100000, 999999)))[:8] _, _ = that.testTx.Exec("SAVEPOINT " + spName) db.Tx = that.testTx isSuccess = action(db) if !isSuccess { _, _ = that.testTx.Exec("ROLLBACK TO SAVEPOINT " + spName) } else { _, _ = that.testTx.Exec("RELEASE SAVEPOINT " + spName) } return isSuccess } // 以下原有逻辑不变... } ``` **改动量:** 3 个文件,约 50 行,生产代码零影响(testTx 默认 nil)。 ## 二、链式测试 API ### 4. 新增 [testing_helper.go](d:/work/hotimev1.5/testing_helper.go) - `TestApp` 结构体(包含 `*Application`、`projs TestProj`、`collector *TestCollector`) - `NewTestApp(configPath, TestProj, listeners...)` -- 初始化 Application、注册路由,不启动 HTTP 服务 - `SetupForTest(router Router)` -- 复用 `Run()` 的路由初始化逻辑 - `TestRequest(method, path, body) TestResponse` / `TestRequestWithSession(...)` -- 通过 `httptest` 发请求 - `TestResponse` 结构体 - `RunTests(t)` -- 遍历 TestProj,每个方法级别 `BeginTestTx() + defer RollbackTestTx()` ### 5. 新增 [testing_api.go](d:/work/hotimev1.5/testing_api.go) 注册类型: - `TestProj map[string]TestProjDef` - `TestProjDef struct { Proj Proj; Tests ProjTest }` - `ProjTest map[string]CtrTest` - `CtrTest map[string]ApiTestDef` - `ApiTestDef struct { Desc string; Func func(a *Api) }` 链式 API: - `Api` -- 起点,提供 `JSON/Query/Form/File/WithSession` 数据设置和 `Get/Post/Put/Delete` 直接终端 - `ApiCase` -- 链式构建器,支持叠加多种数据类型,终端操作 `Get/Post/Put/Delete(desc, status, msg...)` - `ApiResponse` -- 响应对象,提供 `GetStatus/GetResult/GetMsg/GetBody/Fail` - 每个终端方法内自动记录 `TestRecord` 到 `TestCollector` ### 6. 新增 [testing_swagger.go](d:/work/hotimev1.5/testing_swagger.go) - `GenerateSwagger(title, version, outputDir)` -- 遍历 TestProj 生成合并的 OpenAPI 3.0 JSON + Swagger UI HTML - 利用覆盖率数据,未覆盖接口在文档中标注"暂无测试用例" ## 三、覆盖率追踪 集成在 `testing_helper.go` 中: - `CoverageReport` / `MethodCoverage` / `TestRecord` / `TestCollector` 类型 - `PrintCoverage()` -- 对比 `Proj`(所有路由)和 `ProjTest`(有测试的路由),输出覆盖率报告 - 运行时追踪:每个 `Post/Get/Put/Delete` 终端方法自动收集 `TestRecord`(路径、用例名、通过/失败、耗时) - 最终输出:总接口数、已覆盖数、覆盖率百分比、每个接口的用例数和通过/失败计数 ## 四、业务层接入(xbc 示例) ### 7. 修改 [xbc/app/user.go](d:/work/xbc/app/user.go) -- 末尾添加 `var UserTest = CtrTest{...}` ```go var UserTest = CtrTest{ "login": {"用户登录", func(a *Api) { a.JSON(Map{"name": "138", "password": "123456"}).Post("正常登录", 0) // ... }}, "create": {"用户注册", func(a *Api) { phone := "138" + ObjToStr(RandX(10000000, 99999999)) // 不需要 defer 清理,事务自动回滚 a.JSON(Map{"phone": phone, "password": "123456"}).Post("正常注册", 0) a.JSON(Map{"phone": phone, "password": "123456"}).Post("重复注册", 3, "该手机号已被注册") }}, } ``` ### 8. 修改 [xbc/app/init.go](d:/work/xbc/app/init.go) -- 添加 `var ProjectTest = ProjTest{...}` ### 9. 新增 [xbc/app/app_test.go](d:/work/xbc/app/app_test.go) ```go func TestMain(m *testing.M) { testApp = NewTestApp("../config/config.json", TestProj{ "app": {Project, ProjectTest}, }) code := m.Run() testApp.PrintCoverage() testApp.GenerateSwagger("小帮菜 API", "2.1.0", testApp.Config.GetString("tpt")) os.Exit(code) } func TestApi(t *testing.T) { testApp.RunTests(t) } ``` ## 五、文件改动汇总 - **修改** `d:/work/hotimev1.5/db/db.go` -- 加 testTx 字段 + BeginTestTx/RollbackTestTx - **修改** `d:/work/hotimev1.5/db/query.go` -- Query/Exec 各加 testTx 判断 - **修改** `d:/work/hotimev1.5/db/transaction.go` -- Action 加 SAVEPOINT 分支 + testTx 传递 - **新增** `d:/work/hotimev1.5/testing_helper.go` -- TestApp、RunTests、覆盖率 - **新增** `d:/work/hotimev1.5/testing_api.go` -- 链式 API + TestCollector - **新增** `d:/work/hotimev1.5/testing_swagger.go` -- Swagger 生成 - **修改** `d:/work/xbc/app/user.go` -- 末尾添加 UserTest - **修改** `d:/work/xbc/app/init.go` -- 添加 ProjectTest - **新增** `d:/work/xbc/app/app_test.go` -- 测试入口