侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

实现表格驱动测试并正确处理错误场景

2025-12-12 / 0 评论 / 4 阅读

题目

实现表格驱动测试并正确处理错误场景

信息

  • 类型:问答
  • 难度:⭐⭐

考点

表格驱动测试设计,错误处理测试,测试辅助函数使用

快速回答

本题考察表格驱动测试的实现和错误场景处理能力,核心要点包括:

  • 使用[]struct{}定义测试用例表格
  • 通过t.Run()为每个用例创建独立子测试
  • 使用errors.Is/errors.As精确比较错误
  • 处理预期错误和正常结果的不同断言逻辑
  • 利用t.Cleanup清理测试资源
## 解析

原理说明

表格驱动测试是Go测试的核心模式,通过结构体切片定义多组输入/输出,循环执行测试逻辑。关键优势:

  • 减少重复代码
  • 统一管理测试数据
  • 支持动态添加用例
  • 子测试隔离保证独立性

错误测试需注意:

  • 区分错误类型(sentinel error/自定义error)
  • 错误值比较需使用errors.Is而非==
  • 验证错误消息的稳定性(避免依赖易变字符串)

代码示例

被测函数:

// 文件:mathutil.go
type DivisionError struct {
    Dividend int
    Divisor  int
}

func (e DivisionError) Error() string {
    return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func SafeDivide(a, b int) (int, error) {
    if b == 0 {
        return 0, DivisionError{Dividend: a, Divisor: b}
    }
    return a / b, nil
}

测试实现:

// 文件:mathutil_test.go
func TestSafeDivide(t *testing.T) {
    tests := []struct {
        name        string
        a, b        int
        want        int
        expectError bool
        errType     error
    }{{
        name:        "normal division",
        a:           10,
        b:           2,
        want:        5,
        expectError: false,
    }, {
        name:        "divide by zero",
        a:           5,
        b:           0,
        expectError: true,
        errType:     DivisionError{},
    }, {
        name:        "invalid divisor",
        a:           7,
        b:           0,
        expectError: true,
        errType:     DivisionError{},
    }}

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := SafeDivide(tt.a, tt.b)

            if tt.expectError {
                if err == nil {
                    t.Fatal("expected error but got nil")
                }
                // 精确错误类型匹配
                if !errors.As(err, &tt.errType) {
                    t.Errorf("unexpected error type: %T", err)
                }
                // 验证错误内容
                var divErr DivisionError
                if errors.As(err, &divErr) {
                    if divErr.Dividend != tt.a || divErr.Divisor != tt.b {
                        t.Errorf("error contains wrong values: %v", divErr)
                    }
                }
            } else {
                if err != nil {
                    t.Fatalf("unexpected error: %v", err)
                }
                if got != tt.want {
                    t.Errorf("got %d, want %d", got, tt.want)
                }
            }
        })
    }
}

最佳实践

  • 用例命名:使用描述性name字段,测试失败时快速定位
  • 错误处理
    • 优先比较错误类型而非字符串内容
    • 使用errors.Is检查sentinel errors
    • 使用errors.As提取自定义错误字段
  • 资源管理:在t.Run内使用t.Cleanup释放资源
  • 测试组织
    • 正常路径和错误路径分开测试
    • 边界值单独作为测试用例

常见错误

  • 错误比较不准确
    // 错误做法:直接比较error对象
    if err != tt.expectedErr { ... }
    
    // 正确做法:使用errors.Is/As
    if !errors.Is(err, tt.expectedErr) { ... }
  • 子测试未隔离:未使用t.Run导致测试状态污染
  • 错误处理遗漏:未检查expectError为true时err为nil的情况
  • 过度断言:严格匹配错误字符串(应优先验证错误类型)

扩展知识

  • Golden文件:复杂输出可使用testdata目录存储预期结果
  • 测试覆盖率:运行go test -coverprofile=coverage.out生成报告
  • 并行测试:在t.Run内调用t.Parallel()加速测试
  • 测试辅助工具
    • cmp.Diff:复杂结构体比较
    • httptest:HTTP处理测试
    • testify/assert:第三方断言库