
go 单元测试中应优先检查 error 是否为 nil,避免依赖错误字符串;推荐使用 errors.is 或类型断言验证自定义错误类型,以提升测试健壮性与可维护性。
go 单元测试中应优先检查 error 是否为 nil,避免依赖错误字符串;推荐使用 errors.is 或类型断言验证自定义错误类型,以提升测试健壮性与可维护性。
在 Go 中,error 是一个接口(type error interface { Error() string }),其设计哲学强调语义化错误处理而非字符串匹配。因此,单元测试时不应简单地 assert.Equal(t, err.Error(), "expected message")——错误消息属于面向用户的输出,可能随版本迭代调整,导致测试脆弱且难以维护。
✅ 正确做法分三层,按推荐优先级递进:
1. 基础检查:非空判断(最常用)
func TestDivide_ValidInput(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatal("expected no error, got:", err)
}
if result != 5 {
t.Errorf("expected 5, got %d", result)
}
}
func TestDivide_DivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Fatal("expected error when dividing by zero")
}
}
2. 语义化校验:使用 errors.Is(推荐用于标准/包装错误)
当函数返回 fmt.Errorf("...: %w", originalErr) 或调用 errors.New / errors.New 包装的错误时,用 errors.Is 判断是否为某类底层错误:
var ErrDivideByZero = errors.New("division by zero")
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide %d by zero: %w", a, ErrDivideByZero)
}
return a / b, nil
}
func TestDivide_ByZero_ErrorIs(t *testing.T) {
_, err := Divide(10, 0)
if !errors.Is(err, ErrDivideByZero) {
t.Errorf("expected wrapped ErrDivideByZero, got %v", err)
}
}
3. 类型级断言:自定义错误结构(需精确状态校验)
当错误需携带上下文(如 HTTP 状态码、重试次数),定义结构体错误并实现 error 接口:
type ValidationError struct {
Field string
Message string
Code int
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
func ValidateEmail(email string) error {
if !strings.Contains(email, "@") {
return &ValidationError{Field: "email", Message: "missing @", Code: 400}
}
return nil
}
func TestValidateEmail_CustomError(t *testing.T) {
err := ValidateEmail("invalid-email")
var ve *ValidationError
if !errors.As(err, &ve) {
t.Fatal("expected *ValidationError")
}
if ve.Field != "email" || ve.Code != 400 {
t.Errorf("unexpected validation error fields: %+v", ve)
}
}
⚠️ 注意事项:
- ❌ 避免
err.Error() == "xxx":违反错误封装原则,易因日志优化或翻译失效; - ✅ 优先
errors.Is和errors.As:它们是 Go 标准库提供的、符合错误链语义的官方推荐方式; - ? 自定义错误应保持轻量,仅暴露测试必需字段,避免泄露内部实现细节;
- ? 在
init()或包级变量中定义哨兵错误(如var ErrNotFound = errors.New("not found")),便于复用和errors.Is判断。
总结:Go 的错误测试核心是关注“发生了什么错误”,而非“错误怎么说”。通过 nil 检查建立基础防线,借助 errors.Is 和 errors.As 实现可扩展、可演进的语义化断言,才能写出真正稳健、面向未来的单元测试。










