Go测试中应优先用errors.Is匹配预定义错误、errors.As提取自定义错误类型,避免直接比较错误指针或断言错误字符串;断言错误时需覆盖成功与失败双路径,显式接收错误变量并用t.Fatal/t.Errorf明确报错。
最基础也最容易出错的一步:只检查 err != nil,却不处理成功路径的逻辑验证。很多测试看似通过,实则漏掉了“本该报错却没报”或“不该报错却报了”的关键场景。
t.Fatal 或 t.Errorf 明确指出预期与实际差异if err == nil { t.Fatal("expected error") } —— 语义绕、易读性差,且一旦 err 为 nil,后续逻辑可能被跳过导致误判_, _ := fn() 忽略返回值;错误变量必须显式接收才能验证当你导出类似 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 —— 一旦错误被包装,指针比较必然失败assert.ErrorIs(t, err, ErrNotFound),语义更直白当错误是结构体(比如 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。
if !strings.Contains(err.Error(), "timeout") { t.Error("missing timeout hint") }
err.Error() == "i/o timeout" —— 拼写、空格、标点微调就会让测试突然失败真正难的不是写对一个断言,而是理解错误在 Go 中是值、是链、是接口,不是字符串。很多人卡在 errors.As 传参传错地址,或在包装错误里还执着用类型断言,结果测得越勤,离真实行为越远。