testify/assert 和 testify/require 是嵌入测试逻辑的控制流决策点,需根据场景选 assert(继续执行)或 require(熔断终止);json 比较用 assert.jsoneq 而非字符串相等;自定义错误用 assert.erroras 提取类型;表驱动测试中每个断言须带 case 上下文。

testify/assert 和 testify/require 不是“加个库就能优雅”的装饰品,而是要嵌进测试逻辑毛细血管里的控制流决策点。微服务场景下,错误传播、依赖隔离、响应结构不稳等问题会让原生 t.Errorf 迅速失效——直接用 testify 本身不解决问题,关键是怎么选、怎么拆、怎么带上下文。
assert.NoError 不能代替 require.NoError,尤其在 HTTP handler 或 DB 查询后
微服务测试里最常见误用:解析 JSON 或调用数据库后只用 assert.NoError(t, err),接着就访问 resp.Data 或 user.ID。一旦 err 非 nil,resp 或 user 是零值,后续字段访问要么 panic,要么产生假阳性断言。
- 必须用
require.NoError(t, err)做前置守门——它调用t.Fatal,立刻终止当前测试函数 -
require.NoError不返回bool,不能写成if require.NoError(...) { ... },会编译失败 - 典型位置:HTTP 测试中
json.Unmarshal(body, &resp)后、Repo 方法返回后、配置加载后
JSON 响应别比字符串,用 assert.JSONEq 处理字段顺序与空格敏感问题
微服务 API 返回体含时间戳、UUID、浮点数或字段顺序不固定时,assert.Equal(t, `{"id":1,"name":"foo"}`, string(body)) 极其脆弱——哪怕多一个空格、float 小数位不同、键顺序一换就失败。
- 改用
assert.JSONEq(t, wantJSON, string(body)),它先json.Unmarshal两边再语义比较,忽略格式与键序 - 非法 JSON 会导致 panic,所以要在
assert.JSONEq前加一层校验:assert.JSONSchema(需额外引入)或手动json.Valid - 若响应含动态字段(如
"created_at": "2026-07-01T02:35:00Z"),先用strings.ReplaceAll或正则抹掉,再喂给assert.JSONEq,不要试图在 JSON 字符串里写模糊匹配
自定义 error 类型必须用 assert.ErrorAs,不是 assert.Equal
微服务常封装错误,比如 fmt.Errorf("failed to fetch user: %w", ErrUserNotFound)。用 assert.Equal(t, ErrUserNotFound, err) 永远失败——assert.Equal 只比 err.Error() 字符串,不递归 errors.Unwrap(),也不识别底层类型。
- 正确做法是
assert.ErrorAs(t, err, &target),其中target必须是非 nil 指针,例如new(*UserNotFoundError) -
assert.ErrorIs适合检查是否包装了某个已知错误(如os.ErrNotExist),ErrorAs才能提取并断言具体类型字段 - 如果
err是 nil,ErrorAs直接失败;如果target是 nil 指针(如nil或未取地址),会 panic
表驱动测试里每个 assert 都要带 case 上下文,否则 CI 日志无法定位问题
微服务接口往往有十几种输入组合(空参、超长 ID、非法 token、并发冲突等),全塞进一个 t.Run 里又不加标识,失败时日志只显示 xxx_test.go:42,根本分不清是哪个 case 挂了。
- 必须在每个
assert.Xxx调用末尾传描述性消息:assert.Equal(t, tc.want, got, "case %q", tc.name) - 更稳妥的是把
tc.name作为t.Run(tc.name, func(t *testing.T) { ... })的名称,再复用它到断言消息里,避免手误错配 - 特别注意
tc.name为空或重复时,消息完全失效——建议强制校验tc.name != ""并 panic 提前暴露
assert 或 require 的调用之前,而不是之后。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











