go test -cover 覆盖率不准是因为它仅统计源码行是否执行,不判断分支、错误路径或副作用是否真实覆盖;如 if err != nil 分支未触发但 if 语句执行了,整行仍被标为覆盖。

为什么 go test -cover 显示的覆盖率不准
Go 的覆盖率统计基于源码行是否被执行,但不区分逻辑分支、条件组合或错误路径是否真实覆盖。比如一个 if err != nil 分支只在测试中 mock 成功路径,那 err != nil 对应的代码块虽然“存在”,却没被运行,但 go test -cover 仍可能把整行标为已覆盖(因 if 语句本身执行了)。实际中常见误判场景包括:
- 使用
testify/mock或gomock时,只验证 happy path,未构造失败返回 - 接口方法有多个实现,但测试只调用默认实现,其他实现体未触发
- 日志、panic、recover 等非主干逻辑未显式触发,但所在行被计入覆盖率
如何强制覆盖 error 分支:用 errors.New 和 io.EOF 模拟失败
很多函数的 error 路径藏在底层调用里,比如 json.Unmarshal、os.Open、http.Do。直接依赖真实 I/O 或网络不可控,也不利于隔离测试。正确做法是通过 interface 抽离依赖,再注入可控返回值。
例如对一个读配置的函数:
func LoadConfig(r io.Reader) (*Config, error) {
dec := json.NewDecoder(r)
var c Config
if err := dec.Decode(&c); err != nil {
return nil, fmt.Errorf("decode config: %w", err)
}
return &c, nil
}
测试时传入 bytes.NewReader([]byte("")) 会触发 json: EOF;更明确的做法是用 io.ErrUnexpectedEOF 或自定义 error:
type errReader struct{ err error }
func (e errReader) Read(p []byte) (int, error) { return 0, e.err }
// 在测试中:
cfg, err := LoadConfig(errReader{errors.New("read failed")})
if err == nil {
t.Fatal("expected error")
}
测试 panic 和 recover:用 recover() 捕获并断言 panic 值
Go 中 panic 不是 error,不会被普通 if err != nil 捕获,必须用 defer + recover 显式处理。若函数文档声明 “panics when X”,就必须测它 —— 否则覆盖率看似高,实际线上一碰就崩。
典型写法:
func MustParse(s string) *regexp.Regexp {
r, err := regexp.Compile(s)
if err != nil {
panic(fmt.Sprintf("invalid regex %q: %v", s, err))
}
return r
}
func TestMustParse_PanicsOnInvalid(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expected panic")
} else if msg, ok := r.(string); !ok || !strings.Contains(msg, "invalid regex") {
t.Fatalf("unexpected panic value: %v", r)
}
}()
MustParse("[unclosed")
}
注意:recover() 只在 defer 函数中有效,且只能捕获当前 goroutine 的 panic。
覆盖率提升的关键不是“凑行数”,而是补全边界条件和副作用路径
很多人用 -covermode=count 看哪行没跑,然后硬加一行调用 —— 这没意义。真正该补的是:
- 输入为
nil、空字符串、负数、超长 slice 时的行为 - 依赖返回
context.Canceled或context.DeadlineExceeded - 并发调用下 data race 或状态竞争(需配合
go test -race) - 日志是否按预期输出(可重定向
log.SetOutput到bytes.Buffer检查)
这些路径往往不增加代码行,但决定系统健壮性。一旦漏掉,go test -cover 数字再高也没用。










