Files
interview/questions/13-Golang语言/错误处理和测试.md
yasinshaw 7f3ab362b3 feat: rename Golang files to Chinese and supplement root files
Changes:
- Renamed all 10 Golang files from English to Chinese names
- Created 00-项目概述/项目概述.md with comprehensive project overview
- Created 08-算法与数据结构/算法与数据结构学习指南.md with detailed learning guide
- Created 12-面试技巧/面试准备进度.md with progress tracking
- Added .obsidian configuration for better markdown editing
- Updated Claude.MD with Chinese filename rule

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:33:32 +08:00

6.6 KiB
Raw Blame History

Golang 错误处理和测试

问题

  1. Go 的错误处理机制是什么?为什么不用 try-catch
  2. panic 和 recover 的使用场景是什么?
  3. errors.Wrap 和 errors.Is 的作用是什么?
  4. Go 的单元测试如何写?有哪些最佳实践?
  5. table-driven test 是什么?
  6. Go 的基准测试Benchmark如何写

标准答案

1. Go 的错误处理

error 接口

type error interface {
    Error() string
}

特点

  • 错误是值,不是异常
  • 必须显式处理
  • 避免嵌套过深

创建错误

import "errors"

// 1. 简单错误
err1 := errors.New("something went wrong")

// 2. fmt.Errorf
err2 := fmt.Errorf("invalid user: %s", "alice")

// 3. 错误包装Go 1.13+
if err != nil {
    return fmt.Errorf("query failed: %w", err)
}

处理错误

func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read file failed: %w", err)
    }
    return data, nil
}

2. panic 和 recover

panic

用途:不可恢复的错误(程序无法继续运行)

// panic 示例
func doPanic() {
    panic("something terrible happened")
}

func main() {
    doPanic()
    fmt.Println("this will not print")
}

recover

用途:捕获 panic防止程序崩溃

func safeExecute() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("recovered from:", err)
        }
    }()

    panic("oh no!")
}

func main() {
    safeExecute()
    fmt.Println("program continues")
}

最佳实践

  1. 避免使用 panic:除非是真正不可恢复的错误
  2. recover 只在 defer 中使用:其他地方无效
  3. 库代码不应 panic:应该返回 error

3. 错误包装和判断

errors.Wrap

import (
    "errors"
    "fmt"
)

func queryDB() error {
    return errors.New("connection failed")
}

func service() error {
    err := queryDB()
    if err != nil {
        return fmt.Errorf("service failed: %w", err)  // 包装错误
    }
    return nil
}

func main() {
    err := service()
    if err != nil {
        // 检查错误链
        if errors.Is(err, errors.New("connection failed")) {
            fmt.Println("is connection error")
        }

        // 获取最底层的错误
        var connErr error = errors.New("connection failed")
        if errors.Is(err, connErr) {
            fmt.Println("connection error occurred")
        }
    }
}

自定义错误类型

import "errors"

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

func validate(name string) error {
    if name == "" {
        return &ValidationError{Field: "name", Message: "required"}
    }
    return nil
}

func main() {
    err := validate("")
    var ve *ValidationError
    if errors.As(err, &ve) {
        fmt.Println("validation error:", ve.Field, ve.Message)
    }
}

4. 单元测试

基本测试

package mypackage

import "testing"

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    result := Add(1, 2)
    if result != 3 {
        t.Errorf("Add(1, 2) = %d; want 3", result)
    }
}

Table-Driven Test表格驱动测试

func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        a, b int
        want int
    }{
        {1, 2, 3},
        {2, 3, 5},
        {10, -5, 5},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

子测试Subtests

func TestAll(t *testing.T) {
    t.Run("Test1", func(t *testing.T) {
        // Test1
    })

    t.Run("Test2", func(t *testing.T) {
        // Test2
    })
}

5. 基准测试Benchmark

基本 Benchmark

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

运行

go test -bench=. -benchmem

Benchmark 高级用法

func BenchmarkAddParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(1, 2)
        }
    })
}

func BenchmarkLargeSlice(b *testing.B) {
    b.ResetTimer()  // 重置计时器
    data := make([]int, 1000000)
    b.StartTimer()

    for i := 0; i < b.N; i++ {
        _ = len(data)
    }
}

6. Mock 和测试替身

使用接口进行 Mock

type Database interface {
    Query(id int) (string, error)
}

type Service struct {
    db Database
}

func (s *Service) GetUser(id int) (string, error) {
    return s.db.Query(id)
}

// Mock 实现
type MockDatabase struct {
    MockQuery func(id int) (string, error)
}

func (m *MockDatabase) Query(id int) (string, error) {
    return m.MockQuery(id)
}

// 测试
func TestService_GetUser(t *testing.T) {
    mockDB := &MockDatabase{
        MockQuery: func(id int) (string, error) {
            return "Alice", nil
        },
    }

    service := &Service{db: mockDB}
    name, err := service.GetUser(1)

    if err != nil {
        t.Errorf("unexpected error: %v", err)
    }
    if name != "Alice" {
        t.Errorf("got %s; want Alice", name)
    }
}

7. 测试最佳实践

1. 测试覆盖率

# 查看覆盖率
go test -cover

# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

2. 测试隔离

// 每个测试独立
func Test1(t *testing.T) {
    // setup
    // test
    // teardown
}

func Test2(t *testing.T) {
    // 不依赖 Test1
}

3. 使用 TestMain

func TestMain(m *testing.M) {
    // 全局 setup
    fmt.Println("setup before all tests")

    m.Run()

    // 全局 teardown
    fmt.Println("teardown after all tests")
}

8. 阿里 P7 加分项

深度理解

  • 理解 Go 错误处理的设计哲学(显式优于隐式)
  • 理解 panic/recover 的实现机制
  • 理解 table-driven test 的优势

实战经验

  • 有编写高质量单元测试的经验(覆盖率 > 80%
  • 有使用 Mock 进行测试的经验
  • 有编写 Benchmark 进行性能优化的经验

测试策略

  • 理解如何设计可测试的代码(依赖注入、接口)
  • 理解如何进行集成测试和端到端测试
  • 理解如何设计测试用例(边界条件、异常情况)