测试进阶:表驱动与接口级回归
面试问法:GoFrame 项目里你怎么做单测和接口回归?覆盖率怎么保证? 一句话结论:纯逻辑用 gtest 表驱动断言;接口层测试让真实 Server 跑起来再用 HTTP 客户端请求断言;数据层用独立测试库(用例间清理)。表驱动覆盖“输入矩阵”,接口回归覆盖“路由+序列化+统一返回”整体正确性,两者互补。
gtest 表驱动
func Test_CheckName(t *testing.T) {
cases := []struct {
name string
in string
want bool
}{
{name: "合法", in: "john", want: true},
{name: "过短", in: "a", want: false},
{name: "超长", in: strings.Repeat("a", 31), want: false},
}
for _, c := range cases {
gtest.C(t, func(t *gtest.T) {
t.Assert(CheckName(c.in), c.want) // 失败只终止当前分组
})
}
}
gtest.C(t, ...)创建分组,断言失败不连累其他分组;断言方法 Assert/AssertEQ/AssertNil/AssertIN 等覆盖常见比较。- 复杂断言可搭配 testify/goconvey(官方文档说明支持)。
接口级回归
func Test_UserAPI(t *testing.T) {
s := g.Server("test-server") // 真实启动,测试专用端口
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(g *ghttp.RouterGroup) { g.Bind(new(v1.Controller)) })
s.SetPort(28080)
s.Start()
defer s.Shutdown()
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix("http://127.0.0.1:28080")
t.Assert(c.GetContent(ctx, "/user/1"), `{"code":0,"data":{...}}`) // 固定测试数据
})
}
- 用例矩阵建议覆盖:正常路径、参数校验失败(各 v 规则)、错误码路径(404/无权限)、分页边界。
- 返回结构较复杂时可用固定期望文件(golden)对比,或只断言 code 与关键字段。
数据与 CI
- 数据层测试用独立测试库/容器(如 Docker 起 MySQL),每个用例前后清理数据,避免互相污染;轻量场景可评估 sqlite 内存驱动(contrib 提供,能力以官方文档为准)。
- service 接口化(gen service 的产物)天然支持替换实现做 mock。
- CI 里跑
go test -race ./...+ 覆盖率门槛;测试配置与本地/生产配置分离。
常见追问
- 追问:单测和接口回归的比例怎么定?——纯业务规则与工具函数重点单测;接口回归按“一接口一冒烟 + 关键场景矩阵”做,别把全部断言堆在接口层(慢且脆)。
- 追问:为什么接口测试要起真实 Server?——规范路由 handler 绑定在 ghttp.Request 上,真实 Server 才验证路由注册、绑定校验、序列化与统一响应的完整链路。
记忆点
- 表驱动管逻辑矩阵,真 Server 接口回归管整链路;gtest 分组隔离 + 独立测试数据 + race/覆盖率进 CI。