D
AI
学习工作台
8 周后端冲刺2026-05-222 分钟阅读

Go 错误处理与测试

error 链、table-driven test、benchmark 与 mock

8周冲刺week3Goerrortesting记笔记标记疑惑

Go 的错误哲学

Go 无 exception:返回 error 作为值。调用方必须显式处理。

f, err := os.Open("config.toml")
if err != nil {
    return fmt.Errorf("open config: %w", err)
}
defer f.Close()

自定义 error

type NotFoundError struct {
    ID string
}

func (e *NotFoundError) Error() string { return "not found: " + e.ID }

Sentinel:var ErrNotFound = errors.New("not found")

错误链

if err != nil {
    return fmt.Errorf("service get user: %w", err)
}

if errors.Is(err, sql.ErrNoRows) { / ... / }

var nfe *NotFoundError if errors.As(err, &nfe) { / use nfe.ID / }

面试强调:不要 _ = err 忽略;日志打 %+v 需 pkg/errors 或 zap 字段。

panic 与 recover

panic 用于 不可恢复编程错误;HTTP handler 最外层 recover 转 500。业务逻辑用 error,少用 panic。

testing 包

func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"pos", 1, 2, 3},
        {"zero", 0, 0, 0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Fatalf("got %d want %d", got, tt.want)
            }
        })
    }
}

httptest 测 Handler;sqlmock 测 DB 层(interface 注入见 week02-go-struct-interface)。

benchmark

func BenchmarkParse(b *testing.B) {
    data := loadFixture()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Parse(data)
    }
}

-benchmem 看分配;对比优化前后 ns/op 与 allocs/op。

工程实践

  • 错误信息:动词 + 对象 + 原因,带 context。
  • 单元测试覆盖边界:nil、空 slice、并发(-race)。
  • CI:go test ./... -race -count=1

与后端面试

讲项目时说明:如何测试 RPC如何 mock 依赖线上 error 如何分级告警

下一篇 week03-go-memory-gc 讲性能与 GC,与 benchmark 呼应。

多错误聚合与可观测性

Go 1.20+ 支持 errors.Join(errs ...error),适合并行校验多个字段失败时一次性返回。日志层应记录 error chain 全链路,便于 Sentry 等工具按根因分组。HTTP 层映射:4xx 业务可预期(参数错误),5xx 未预期(依赖超时)。可重试错误 应包装 sentinel ErrTemporary,调用方配合指数退避。

测试替身与集成测试

interface 注入 Repository 后,单测用 fake 内存实现;集成测用 testcontainers 起 MySQL/Redis。t.Parallel() 加速无共享状态的 case,共享全局 map 的测试禁止 parallel。-coverprofile 关注核心包覆盖率,但 100% 覆盖率不等于无 bug,重点覆盖分支与 error path。

面试追问示例

「线上 panic 如何处理?」→ 最外层 middleware recover,打 stack,返回统一 JSON,异步告警。「table-driven 如何测 error?」→ wantErr true + errors.Is 断言类型。结合 week02-go-struct-interface 的 mock 模式完整回答依赖隔离。

补充要点

t.Helper() 标记辅助函数;TestMain 做全局 setup。fuzzing Go 1.18+ 发现边界输入。mock 代码生成 mockgen;集成测试 tag build。CI 必须 -race。

错误处理反模式

切忌 if err != nil { log.Println(err) } 后继续执行,导致脏数据写入。切忌用 panic 控制业务流程。跨 goroutine 错误用 errgroup 收集第一个 error 并 cancel 兄弟任务。对外 API 不要把内部 stack 直接返回给用户,映射为业务 code 与 message,详细链写日志。

测试分层策略

单元测试覆盖纯函数与 domain 逻辑;集成测试验证 SQL 与 Redis 行为;端到端测试少量黄金路径。表驱动 case 命名应描述场景,如「空输入」「重复提交」。benchmark 对比优化前后 allocs,与 week03-go-memory-gc 联动。面试时主动说 CI 跑 race detector,发布前跑回归套件,体现工程规范。

知识卡片

问题

errors.Is 与 errors.As 区别?

点击翻转查看答案

答案

Is 比较 error 链中是否含某 sentinel;As 将链中某层 error 赋给目标指针类型,用于结构化错误。

问题

table-driven test 好处?

点击翻转查看答案

答案

用切片存多组 input/expected,循环 t.Run 子测试;易加 case、失败信息带 case 名。

问题

fmt.Errorf %w 作用?

点击翻转查看答案

答案

包装 error 保留 unwrap 链,errors.Unwrap 可遍历;便于上层 Is/As 而不丢失根因。