接口单元测试重在验证各层逻辑契约,Testify的assert、require、mock、suite需按场景正确选用:require用于关键前置条件以避免panic,assert适用于并列字段断言;mock.On必须严格匹配参数类型与顺序;表驱动测试中assert.New(t)须在每个t.Run内创建;suite需显式调用suite.Run才能启用生命周期管理;新项目应优先考虑手工mock而非testify/mock。

接口单元测试不是测HTTP请求通不通,而是测你写的 handler、service、repository 各层逻辑是否按契约执行;Testify 的 assert、require、mock、suite 四个模块必须按场景选对,错配会导致失败定位失焦、并发污染、mock 不生效。
assert.Equal 和 require.Equal 到底该用哪个
区别不在“强弱”,而在“是否继续执行”。assert.Equal 失败只记录错误并返回 false,测试函数照常往下跑;require.Equal 失败直接调用 t.Fatal(),当前测试函数立刻退出。
- 关键前置条件必须用
require:比如require.NoError(t, err)检查 JSON 解析是否成功,再接着断言user.Name—— 若解析失败却用assert,后续访问user.Name会 panic,堆栈指向字段访问行,而非真正出问题的解析处 - 并列独立字段适合用
assert:比如 API 返回结构体{Name, Email, Age},一次断言能同时看到哪几个字段不对,不用拆成三个require分别跑 -
require.Nil(t, err)后的代码不会执行,但assert.Nil(t, err)后若没加if err != nil { return },后续逻辑仍会运行,极易引发空指针或状态错乱
mock.On 总不生效?先盯死参数类型和顺序
根本原因几乎全是签名不匹配:mock.Anything 只绕过值匹配,不绕过类型检查。方法签名是 Do(ctx context.Context, req *UserRequest),你写 mock.On("Do", mock.Anything, mock.Anything),但实际传入的是 *OtherRequest,根本不会命中。
- 必须严格按原方法参数顺序、类型(值 or 指针、具体类型)写
mock.On,不能用interface{}替代具体类型 - 不确定类型时,用
mock.MatchedBy(func(v interface{}) bool { return v != nil && reflect.TypeOf(v).Name() == "UserRequest" })显式判断 - 返回值数量和类型必须完全一致:原方法返回
(int, error),.Return(42, nil)合法,.Return(42)或.Return("abc", nil)运行时报Wrong number or type of return values -
mock.AssertExpectations(t)必须放在测试函数末尾、所有被测代码执行完之后,提前调用会报Expected call at ... never happened
表驱动测试里 assert.New(t) 为什么不能复用
每个 t.Run 子测试都有独立的 *testing.T 实例,共享同一个 assert.Assertions 实例会导致上下文污染——失败日志、计数器、甚至 t.Log 输出都混在一起,且 race detector 可能误报。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:在每个
t.Run内部调用a := assert.New(t),确保断言绑定到当前子测试上下文 - 错误写法:
a := assert.New(t)放在循环外,然后在循环内反复调用a.Equal(...) - 闭包变量捕获也要注意:
for _, tc := range cases { tc := tc; t.Run(..., func(t *testing.T) {...}) },否则所有子测试可能读到同一个tc
suite.Run 不调用,SetupTest 就只是普通方法
testify/suite 不是自动注册机制。定义了 MySuite struct 并实现 SetupTest,若没在测试函数里显式写 suite.Run(t, new(MySuite)),那 SetupTest 和 TestXxx 方法只是普通 Go 方法,生命周期不会被框架接管。
-
SetupTest和TearDownTest每次测试方法执行前后各调用一次,但操作的是同一个结构体实例 —— 所有测试方法共享字段状态 - 因此
suite默认禁用t.Parallel();并发修改同一字段会触发 data race,go test -race会报写-写冲突 - 真要并行,放弃
suite,改用标准t.Run+assert/require,手动管理 setup/teardown -
SetupSuite/TeardownSuite虽然只运行一次,但无法规避单个测试内的竞态 —— 只要字段被多个TestXxx方法读写,就危险
最易被忽略的点:mock 已基本被社区弃用,现在主流做法是定义小接口 + 手工 mock(即实现接口的 fake struct),更轻量、类型安全、无反射开销;testify/mock 在新项目中应优先评估替代方案。

















