Golang中的错误处理与单元测试 Go语言如何模拟并测试错误路径

6次阅读

go单元测试中应通过接口抽象依赖并手写mock控制错误返回,避免真实i/o;Error判断用errors.is/as而非==或字符串匹配;panic测试需用defer+recover精准捕获。

Golang中的错误处理与单元测试 Go语言如何模拟并测试错误路径

Go 单元测试里怎么让函数返回错误

想测错误路径,核心是控制被测函数的依赖——让它在特定条件下返回 error,而不是靠真实 I/O 或网络触发失败。硬等真实错误(比如断网、磁盘满)既慢又不可控,CI 里还容易飘红。

常见错误现象:nil 指针 panic、测试始终走成功分支、mock 返回的 error 被忽略没生效。

  • 用接口抽象依赖,比如把 *http.Client 换成自定义 HTTPDoer 接口,测试时传入返回错误的 fake 实现
  • 避免直接 new Struct 赋值 error 字段(如 err: errors.New("boom")),而是在方法里根据输入条件返回,否则无法覆盖多分支逻辑
  • 如果依赖是第三方库且没接口,用函数变量替代(如声明 var doRequest = http.DefaultClient.Do),测试前重置为返回错误的闭包

用 testify/mock 还是纯 Go 手写 mock

testify/mock 自动生成代码太重,小项目反而增加维护成本;纯 Go 手写 mock 更轻、更可控,也更容易看出“哪里被 stub 了”。

使用场景:中等规模项目、团队对 Go 接口抽象有共识、不希望引入额外构建步骤。

立即学习go语言免费学习笔记(深入)”;

  • 手写 mock 只需实现被测函数依赖的接口,方法体里按需返回 nil 或具体 error,比如 func (m *mockDB) QueryRow(...) (*sql.Row, error) { return nil, sql.ErrNoRows }
  • testify/mock 适合已有大型接口且变更频繁,但要注意生成的 mock 文件要 git commit,否则 CI 构建失败
  • mock 返回的 error 类型要和真实依赖一致(比如 sql.ErrNoRows 而不是 errors.New("not found")),否则 if err == sql.ErrNoRows 判断会失效

error 判断逻辑在测试里怎么写才可靠

== 直接比较 error 值只适用于导出的哨兵错误(如 io.EOF),自定义错误或包装错误(fmt.Errorf("wrap: %w", err))必须用 errors.Iserrors.As

性能影响:两者开销极小,但错用会导致测试误通过或误失败,比性能问题更致命。

  • 检查是否为某类错误:if !errors.Is(err, os.ErrNotExist) { t.Fatal("expected os.ErrNotExist") }
  • 检查是否包含底层错误:var pathErr *os.PathError; if errors.As(err, &pathErr) { ... }
  • 避免写 if err.Error() == "file not found" —— 字符串匹配脆弱,且无法处理 error 包装

panic 和 error 混用时测试怎么覆盖

Go 函数里混用 panicerror 是反模式,但存量代码常有。测试 panic 必须用 recover 捕获,不能靠 defer + log。

容易踩的坑:recover 写在错误调用外层、没重置 goroutine 状态、panic 信息格式和预期不一致。

  • 正确姿势:用 defer func() { if r := recover(); r != nil { /* 检查 r */ } }() 包裹被测调用
  • 如果被测函数在 goroutine 里 panic,主 goroutine 的 recover 捕不到,得在内部加 recover 或改用 channel 同步信号
  • panic 值可能是 String、error 或自定义 struct,别假设一定是 error;用 reflect.typeof(r).kind() == reflect.String 兜底判断

事情说清了就结束。真正难的不是写 mock,而是把依赖拆干净、让 error 能被构造、让 panic 有迹可循——这些设计决策,往往在第一次写函数时就定下了。

text=ZqhQzanResources