表驱动测试需覆盖所有输入分支,包括边界条件如空字符串、缺前缀、含元数据等;mock 要自动生成并显式设置 error 分支;context 取消与超时须用 WithCancel/WithTimeout 主动触发。

怎么用表驱动测试覆盖所有输入分支
表驱动测试不是为了“看起来整齐”,而是确保每个边界条件都被执行到。比如一个解析版本号的函数,不能只测 "v1.2.3",还得构造 "1.2"(缺前缀)、""(空字符串)、"v1.2.3-beta+build.1"(含元数据)等 case。
- 每个
t.Run名称必须包含关键输入,例如t.Run("empty_string_returns_error", ...),方便失败时快速定位 - 结构体字段命名要语义清晰:
input、expectedErr、expectedValue,避免用want这类模糊词 - 对返回 error 的 case,显式检查
err != nil且errors.Is(err, xxxErr),而不是只判非空 - nil 切片和空切片是两个不同分支:传
[]string(nil)和[]string{}都得单独写 case
怎么 mock 接口并覆盖 error 路径
Go 接口 mock 的核心目标不是“能跑通”,而是让错误路径被真实触发。手写 mock 容易漏方法或状态不一致,建议用 testify/mock 自动生成。
- 为每个依赖接口建独立 mock 文件,如
mocks/db.go和mocks/cache.go,防止跨测试污染 - 必须为 error 分支显式设置期望:
mock.On("Find", 123).Return(nil, errors.New("not found")) - 调用
mock.AssertExpectations(t)放在测试末尾,否则 mock 调用没被校验就通过了 - HTTP handler 测试中,别只用
httptest.NewRequest("GET", "/user/1", nil),要主动构造 bad request:httptest.NewRequest("POST", "/user", strings.NewReader(`{"id": "abc"}`))
怎么验证 context 取消和超时逻辑
带 context.Context 参数的函数,90% 的未覆盖行都出在这里。靠 time.Sleep 等超时既不可靠又拖慢测试,必须用可控方式强制走 cancel 分支。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 测试取消:用
ctx, cancel := context.WithCancel(context.Background()); cancel(),再传ctx进函数 - 测试超时:用
ctx, _ := context.WithTimeout(context.Background(), 1*time.Millisecond),确保函数内select中的case 被击中 - 避免在测试里启动 goroutine 后用
time.Sleep(10 * time.Millisecond)等结果,改用chan struct{}或sync.WaitGroup做信号同步 - 如果函数内部调用了
http.Client.Do,记得给 client 设置Timeout,否则 context 超时可能被底层阻塞忽略
怎么定位并补全未覆盖的代码行
go test -cover 输出的百分比数字毫无意义,真正有用的是知道哪一行没执行。很多人看到 85% 就停了,但关键的 if err != nil { log.Fatal() } 可能永远没进过 log.Fatal 分支。
立即学习“go语言免费学习笔记(深入)”;
- 先生成 profile:
go test -coverprofile=cover.out -covermode=count(count模式能区分执行次数,比set更准) - 查函数级覆盖率:
go tool cover -func=cover.out,快速发现哪些函数完全没跑或只有部分行覆盖 - 生成 HTML 报告:
go tool cover -html=cover.out -o cover.html,红块标出具体未执行行 - 重点关注:error 处理块、
default分支、len(s) == 0和s == nil的 if 判断、HTTP 非 200 状态码处理
真正的难点不在写测试,而在判断“这个分支是否可能被真实调用”。比如一个数据库查询函数,你 mock 了 db.Find 返回 error,但没 mock db.Close 的 error,那连接池关闭失败的路径就依然裸奔。覆盖不是堆用例,而是对调用链上每一个可失败点做显式控制。

















