表驱动测试与测试覆盖率实践
一句话结论:表驱动测试把“用例即数据”落到代码里:定义 []struct{name, input, want},for + t.Run 逐个执行,新增用例只加一行数据,失败时 t.Run 的名称直接指出是哪个场景。它最适合纯函数、解析、校验与业务规则;测 Gin handler 用 httptest.NewRecorder + r.ServeHTTP 无需真起端口。覆盖率用 go test -coverprofile 产出报告,go tool cover -html 可视化——覆盖率是“探测器”不是 KPI,重点盯核心逻辑的错误分支、边界与并发(-race)场景,别为凑 100% 写无效用例。
表驱动测试示例
func TestValidateName(t *testing.T) {
cases := []struct {
name string
in string
want bool
}{
{"空串", "", false},
{"超长", strings.Repeat("a", 129), false},
{"合法", "张三", true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := ValidateName(tc.in); got != tc.want {
t.Fatalf("in=%q got=%v want=%v", tc.in, got, tc.want)
}
})
}
}
测 Gin handler
func TestGetUser(t *testing.T) {
r := gin.New()
r.GET("/users/:id", userHandler.Get)
req := httptest.NewRequest("GET", "/users/42", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req) // 不走网络,直接跑路由
if w.Code != 200 { t.Fatalf("status=%d", w.Code) }
var body Resp
json.Unmarshal(w.Body.Bytes(), &body) // 比较反序列化字段而非整串
}
- 依赖隔离:dao/service 用接口定义,测试注入 fake/mock;纯 DB 测试可用 sqlmock 或独立测试库。
- 表驱动里加“错误分支”:参数非法、记录不存在、无权限、DB 错误,这些才是覆盖率该覆盖的地方。
覆盖率实践
go test ./... -cover
go test ./... -coverprofile=cover.out -covermode=atomic -race
go tool cover -html=cover.out # 浏览器可视化未覆盖行
- 覆盖率是手段:行覆盖容易虚高,重点看核心分支是否测到;CI 里可对关键包设门槛(如 service 层 >80%),但别当硬性 KPI 一刀切。
- 补充手段:t.Parallel 提速(注意共享状态)、golden 文件对比复杂输出、Go 1.18+ fuzz 补随机边界。
- 测试金字塔:大量单元测试 + 少量集成测试;handler 测试不依赖真实网络与外部服务。
追问记忆点
- 追问:为什么表驱动里每个用例用 t.Run?——子测试独立失败、独立并行、输出可读,还能 subtests 过滤运行。
- 追问:怎么测中间件?——构造请求直接 r.ServeHTTP 走整条链,或对中间件返回的 HandlerFunc 传入假 Context 单测。
- 追问:100% 覆盖率有意义吗?——无意义用例刷出来的 100% 反而拖慢迭代;覆盖决策点(分支)比覆盖行重要。
- 记忆点:用例即数据 + t.Run 隔离;httptest 测路由无需端口;-coverprofile/-race 进 CI;覆盖率看分支不看虚荣数字。