assert.Equal失败后继续执行,require.Equal则终止测试;表测试中需在每个t.Run内新建assert实例;suite因共享状态禁用并行;mock已趋淘汰,推荐小接口+手工mock。

assert.Equal 和 require.Equal 有什么本质区别?
区别不在功能,而在失败时的行为:前者只报错继续执行,后者直接终止当前测试函数。
常见错误现象:assert.Equal 后面还跟着对 result.Name 的断言,但 result 其实是 nil,导致 panic 报的是空指针而非断言失败——你本该在第一步就发现它不等于预期值。
- 用
assert:适合多个独立断言,比如验证返回结构体的几个字段是否符合预期 - 用
require:适合有依赖关系的断言,比如先检查err == nil,再检查user.ID > 0 -
require.Nil(t, err)失败后,t.Log和后续断言全跳过,不会污染输出或引发 panic
表驱动测试里怎么安全用 assert.New(t)?
不能在循环外创建一次 assert 实例然后复用,因为每个子测试(t.Run)需要自己的 *testing.T 上下文。
使用场景:你有一组输入/输出对,想统一用 assert.Equal 验证,又希望每个失败都带清晰的子测试名。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 正确做法:在每个
t.Run内部调用assert.New(t) - ❌ 错误做法:
a := assert.New(t)放在循环外,然后在t.Run里反复用a.Equal—— 这会导致所有子测试共享同一个t,失败行号错乱、日志混杂 - 性能影响:无。创建
assert.Ata实例开销可忽略,远小于一次 HTTP 调用或 DB 查询
func TestAddTable(t *testing.T) {
cases := []struct{ a, b, want int }{
{1, 2, 3},
{0, 0, 0},
}
for _, tc := range cases {
tc := tc // 防止闭包捕获循环变量
t.Run(fmt.Sprintf("Add(%d,%d)", tc.a, tc.b), func(t *testing.T) {
a := assert.New(t) // 每个子测试单独 new
a.Equal(tc.want, Add(tc.a, tc.b))
})
}
}
为什么 suite.Run 会禁用并行测试?
因为 testify/suite 本质是把测试方法挂载到一个结构体实例上,而这个实例在所有 TestXxx 方法间共享字段状态——一旦两个测试并发修改同一字段,就会触发数据竞争。
典型错误现象:go test -race 报出写-写冲突;某次测试偶尔失败,重跑又通过;TeardownTest 清掉了别的测试刚写入的缓存数据。
-
SetupTest和TeardownTest每个测试前/后各执行一次,但它们操作的是同一个结构体指针 - 如果你真需要并行,别用
suite,改用标准库的t.Parallel()+assert或require -
SetupSuite/TeardownSuite虽然只运行一次,但依然无法规避单个测试内的竞态——只要字段被多个TestXxx方法读写,就危险
mock 包现在还值得用吗?
在 Go 生态里,testify/mock 已基本被接口+组合+手工 mock 取代,尤其自 Go 1.18 泛型普及后,更倾向用轻量方式隔离依赖。
容易踩的坑:生成 mock 代码需额外工具(mockery),维护成本高;mock 对象行为不易追踪;和 gomock 等主流方案不兼容。
- ✅ 推荐替代方案:定义小接口(如
type DB interface { Query(...) }),测试时传入匿名结构体实现 - ❌ 不推荐场景:为一个有 20 个方法的大接口生成 mock——你大概率是在测试设计阶段就该拆分它了
- 兼容性注意:
testify/mock未适配 Go 的新错误包装机制(%w),某些错误断言可能失效


















