You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go单元测试中如何验证无效输入的错误场景?

在Go单元测试中正确预期错误的方案

我来帮你梳理下如何在Go单元测试中优雅地预期错误,同时遵循Go的最佳实践(避免直接比较错误字符串)。首先,我们需要调整你的测试用例结构,让它能同时支持预期成功值和预期错误,然后用正确的方式检查错误。

第一步:重构业务函数,使用自定义错误

Go不建议直接比较错误字符串的核心原因是,错误字符串可能会在后续迭代中修改,导致测试脆弱。更好的做法是定义一个可复用的错误变量,这样测试时可以直接比较错误实例:

import "errors"

// 定义自定义错误变量,供业务逻辑和测试共享
var ErrIndexOutOfBounds = errors.New("index out of bounds")

func arrIdx(arr []int, idx int) (int, error) {
    if idx >= 0 && idx < len(arr) {
        return arr[idx], nil
    }
    // 返回自定义错误,替代临时的fmt.Errorf
    return 0, ErrIndexOutOfBounds
}

第二步:调整测试用例结构体

我们需要修改测试用例的结构体,让它能明确区分每个用例是预期成功(返回具体值)还是预期错误:

import "testing"

func TestArrIdx(t *testing.T) {
    testArr := []int{1, 2, 3, 4, 5}
    
    // 定义测试用例结构体,包含名称、输入、预期值、预期错误
    tests := []struct {
        name    string
        arr     []int
        idx     int
        wantVal int       // 预期成功时的返回值
        wantErr error     // 预期错误时的错误实例
    }{
        {
            name:    "valid index (last element)",
            arr:     testArr,
            idx:     len(testArr)-1,
            wantVal: 5,
            wantErr: nil, // 标记预期无错误
        },
        {
            name:    "valid index (second element)",
            arr:     testArr,
            idx:     1,
            wantVal: 2,
            wantErr: nil,
        },
        {
            name:    "invalid negative index",
            arr:     testArr,
            idx:     -1,
            wantVal: 0, // 该值无意义,因为预期错误
            wantErr: ErrIndexOutOfBounds,
        },
        {
            name:    "invalid out-of-bounds index",
            arr:     testArr,
            idx:     len(testArr),
            wantVal: 0,
            wantErr: ErrIndexOutOfBounds,
        },
    }

第三步:编写测试逻辑,分场景处理预期

在测试循环中,我们需要根据每个用例的预期,分别检查返回值和错误:

for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            gotVal, gotErr := arrIdx(tt.arr, tt.idx)
            
            // 场景1:预期无错误
            if tt.wantErr == nil {
                if gotErr != nil {
                    t.Fatalf("arrIdx() returned unexpected error: %v", gotErr)
                }
                if gotVal != tt.wantVal {
                    t.Errorf("arrIdx() = %d, want %d", gotVal, tt.wantVal)
                }
                return
            }
            
            // 场景2:预期有错误
            if gotErr == nil {
                t.Fatalf("arrIdx() expected error %v, but got nil", tt.wantErr)
            }
            // 使用errors.Is检查错误,支持错误包装场景(Go 1.13+)
            if !errors.Is(gotErr, tt.wantErr) {
                t.Errorf("arrIdx() error = %v, want %v", gotErr, tt.wantErr)
            }
        })
    }
}

关键要点说明

  • 自定义错误变量:这是避免错误字符串比较的核心,让测试更稳定——即使后续错误描述修改,只要错误变量不变,测试依然有效。
  • 分场景处理:明确区分预期成功和预期错误的逻辑,让测试代码更易读、易维护。
  • errors.Is的使用:如果你的错误存在包装(比如用fmt.Errorf("wrap: %w", ErrIndexOutOfBounds)),errors.Is依然能正确识别底层的错误实例,比直接比较更健壮。

如果因为维护老代码等原因无法使用自定义错误变量,你可以退而求其次检查错误类型(如果是自定义错误类型),但强烈不建议直接比较错误字符串,因为这种测试非常脆弱。

内容的提问来源于stack exchange,提问作者Viet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 14:32:37