Go测试中应优先用errors.Is匹配预定义错误、errors.As提取自定义错误类型,避免直接比较错误指针或断言错误字符串;断言错误时需覆盖成功与失败双路径,显式接收错误变量并用t.Fatal/t.Errorf明确报错。

Go测试中如何断言错误是否存在
最基础也最容易出错的一步:只检查 err != nil,却不处理成功路径的逻辑验证。很多测试看似通过,实则漏掉了“本该报错却没报”或“不该报错却报了”的关键场景。
- ✅ 正确姿势:调用后立刻判断,失败时用
t.Fatal或t.Errorf明确指出预期与实际差异 - ❌ 反模式:
if err == nil { t.Fatal("expected error") }—— 语义绕、易读性差,且一旦 err 为 nil,后续逻辑可能被跳过导致误判 - ⚠️ 注意:永远不要用
_, _ := fn()忽略返回值;错误变量必须显式接收才能验证
errors.Is 用于匹配预定义错误值
当你导出类似 var ErrNotFound = errors.New("not found") 这样的包级错误变量时,errors.Is 是唯一安全可靠的匹配方式——它能穿透 fmt.Errorf("wrap: %w", err) 的包装链。
- ✅ 推荐写法:
if !errors.Is(err, ErrNotFound) { t.Errorf("expected ErrNotFound, got %v", err) } - ❌ 千万别写:
err == ErrNotFound—— 一旦错误被包装,指针比较必然失败 - ? 补充:若用 testify/assert,可简化为
assert.ErrorIs(t, err, ErrNotFound),语义更直白
errors.As 用于提取并断言自定义错误类型
当错误是结构体(比如 type ValidationError struct{ Field string }),你不能靠 err.(*ValidationError) 直接断言——包装过的错误会直接 panic 或返回 false。
- ✅ 安全提取:
var ve *ValidationError; if errors.As(err, &ve) { /* 使用 ve.Field */ } - ❌ 错误写法:
if ve, ok := err.(*ValidationError); ok { ... }—— 对fmt.Errorf("%w", ve)完全失效 - ⚠️ 注意:
errors.As第二个参数必须是指针地址(&ve),传值会导致匹配失败
错误消息断言:只在必要时才用,且要克制
校验 err.Error() 内容是最脆弱的断言方式。仅当错误文本本身是对外契约(如 CLI 工具的用户提示)时才考虑,否则优先用 errors.Is 或 errors.As。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 可接受:
if !strings.Contains(err.Error(), "timeout") { t.Error("missing timeout hint") } - ❌ 高危操作:
err.Error() == "i/o timeout"—— 拼写、空格、标点微调就会让测试突然失败 - ? 提示:Go 标准库几乎从不在文档中承诺错误字符串格式,所以把它当成实现细节而非接口
真正难的不是写对一个断言,而是理解错误在 Go 中是值、是链、是接口,不是字符串。很多人卡在 errors.As 传参传错地址,或在包装错误里还执着用类型断言,结果测得越勤,离真实行为越远。










