表驱动测试与测试覆盖率实践

一句话结论:表驱动测试把“用例即数据”落到代码里:定义 []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;覆盖率看分支不看虚荣数字。
笔记加载中…