真实覆盖率需用 go tool cover -func 检测,重点关注 0.0% 或 33.3% 覆盖率函数,它们暴露跨服务错误传播、context 取消链、重试/降级逻辑等未覆盖路径。

微服务里覆盖率数字虚高很常见,真正卡住的是跨服务错误传播、context取消链、重试/降级逻辑这三类路径——它们在单测里不显式构造就永远跑不到。
go tool cover -func 才是真实覆盖率的照妖镜
别只看 go test -cover 输出的总百分比。执行完 go test -coverprofile=coverage.out 后,立刻跑:go tool cover -func=coverage.out。重点关注 coverage 值为 0.0% 或 33.3% 的函数,这些往往对应:
-
if err != nil分支完全没进过,因为 mock 总返回nil - 带
context.Context的 handler,测试没传已 cancel 的 ctx,导致超时路径沉默 -
defer里的清理逻辑(如close(ch)、log.Printf)因提前 return 没执行 - 重试循环中的第 2+ 次失败分支,测试只覆盖了首次调用
error 分支必须手动造,不能等“自然失败”
微服务间调用(gRPC/HTTP)的错误类型高度结构化,靠随机失败根本不可控。mock 必须显式返回特定错误:
- 对 gRPC client,mock
GetUser方法时,至少提供nil, status.Error(codes.NotFound, "user not found")和nil, status.Error(codes.Unavailable, "backend timeout")两条路径 - 对 HTTP client,让
Do返回nil, context.DeadlineExceeded或&http.Response{StatusCode: 503}, nil,验证上游是否正确透传或包装 - 断言时用
errors.Is(err, xxx)而非err != nil,确保语义一致——比如降级逻辑只响应codes.Unavailable,不该被codes.NotFound触发
表格驱动 + context 控制,打穿超时/取消/重试路径
微服务最怕“看起来过了,其实没走通”的逻辑。把 context 构造和重试次数也变成测试表的字段:
- 写一个
testCases := []struct { name string ctxFunc func() context.Context maxRetries int expectFallback bool } - 其中
ctxFunc可以是:func() context.Context { ctx, cancel := context.WithCancel(context.Background()); cancel(); return ctx }(立即取消) - 或
func() context.Context { return context.WithTimeout(context.Background(), 1*time.Millisecond) }(必然超时) - 重试场景下,mock 底层调用按次数返回错误:第一次
codes.Unavailable,第二次nil,验证是否真重试了两次
拆函数 + 接口注入,让不可测逻辑可测
直接在 handler 里调 time.Now()、rand.Intn()、全局 grpc.Dial 实例,等于给测试埋雷。必须重构:
- 把时间依赖抽成函数变量:
var nowFunc = time.Now,测试时替换为固定时间 - 把重试策略抽象为接口:
type Backoff interface { Next() time.Duration },测试时注入返回0或1ms的实现 - 所有外部 client(gRPC/HTTP)通过 interface 注入,避免直接 new —— 这样才能在测试里精准控制返回值和错误类型
真正难覆盖的从来不是代码行数,而是那些“只有线上流量才能触发”的错误传播链和状态切换点。盯住 go tool cover -func 里红得刺眼的 0.0%,一个个补,比盲目追求 90% 数字有用得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











