应优先使用 testify/assert 或 testify/require 替代手写 if + t.Errorf,避免无 diff 错误信息、类型敏感问题及 panic 掩盖真实失败;断言前须先求值,JSON 比较用 assert.JSONEq,错误校验用 assert.ErrorAs 或 assert.ErrorIs,前置检查用 require.NoError 确保后续安全访问。

别用 t.Errorf 手写 if 判断,直接上 testify/assert 或 testify/require —— 否则错误信息没 diff、类型敏感问题堆成山、前置失败后还硬跑导致 panic 掩盖真实问题。
断言前必须先求值,不能把调用和断言写在同一行
常见错误是这样写:assert.Equal(t, "ok", riskyFunc())。一旦 riskyFunc() panic,测试直接崩溃,根本看不到断言失败的上下文,日志里只有 goroutine panic 堆栈。
- 正确做法:拆成两步,先执行再断言
resp, err := riskyFunc()assert.NoError(t, err)assert.Equal(t, "ok", resp) - 对 JSON 解析、DB 查询、HTTP 调用这类易出错操作,这步不能省
- 如果非得链式调用(比如
json.Marshal().([]byte)),得用defer func(){...}()捕获 panic 并转为 error 再断言
assert.NoError 和 require.NoError 的行为差异决定测试是否可靠
这不是风格偏好,是控制流设计:前者记录错误继续跑,后者失败立刻 t.Fatal 终止当前函数。
- 用
require.NoError做前置守门:JSON 解析后、DB 返回后、配置加载后——只要后续要访问返回值字段,就必须用require,否则resp.User.Name可能 panic - 用
assert.NoError适合独立校验多个不依赖彼此的结果,比如同时检查 status、code、message 字段 -
require.NoError不返回 bool,不能写成if require.NoError(...) { },会编译失败
JSON 响应必须用 assert.JSONEq,别碰字符串相等
assert.Equal(t, `{"id":1,"name":"foo"}`, string(body)) 是典型脆弱写法:字段顺序一变、空格增减、float 小数位不同就失败,但业务其实没坏。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
-
assert.JSONEq先解析两边 JSON,再按语义比较,忽略格式、键序、空白 - 输入非法 JSON 会 panic,所以建议在
assert.JSONEq前加一层assert.JSONSchema(需额外引入)或用json.Valid预检 - 响应含动态字段(如时间戳、UUID)时,先用
strings.ReplaceAll或正则抹掉,再喂给assert.JSONEq;不要试图在 JSON 字符串里写模糊匹配
自定义错误必须用 assert.ErrorAs,不是 assert.Equal
assert.Equal(t, ErrUserNotFound, err) 永远失败——它只比 err.Error() 字符串,不递归 errors.Unwrap(),也不识别包装后的底层类型。
- 正确方式:
var target *UserNotFoundError,然后assert.ErrorAs(t, err, &target);&target必须是非 nil 指针 -
assert.ErrorIs适合检查是否包装了某个已知错误(如os.ErrNotExist),ErrorAs才能提取并验证具体类型 - 结构体含未导出字段时,
assert.Equal无法反射访问,此时也建议改用assert.JSONEq序列化后比对,或手动比关键导出字段
最常被忽略的其实是「断言之后的状态」:require 熔断是否及时、assert 实例是否隔离、错误类型是否真正提取——这些细节不处理,测试看起来过了,实际掩盖了更深层的问题。

















