应优先用 require——前置条件不满足时直接终止,避免无效执行;assert 适用于循环内多元素验证等需继续执行场景。

testify 的 assert 和 require 到底该用哪个?
核心区别在于失败行为:assert 失败只打日志、继续执行后续断言;require 失败直接 return,跳过当前函数剩余逻辑。多数测试里你应该优先用 require——比如检查返回值非 nil、err 为 nil 这类前置条件,不满足就无意义再往下测。
常见误用:在循环里用 require 导致只测到第一个元素就退出。这时改用 assert 更合理,或者把单次断言抽成子函数 + t.Run 隔离。
-
require.NoError(t, err)比assert.NoError(t, err)更安全,尤其在初始化依赖后 - 结构体字段对比优先用
require.Equal(t, want, got),别手写if !reflect.DeepEqual() - 避免
assert.True(t, len(s) > 0),改用require.NotEmpty(t, s)——语义清晰且失败信息更友好
gomock 生成 mock 时为什么总报 “no buildable Go source files”?
这是 mockgen 扫描包路径时找不到 .go 文件的典型错误,根本原因不是代码问题,而是路径或构建约束没对上。
实操要点:
- 确保目标接口所在包能被正常
go build(比如没有//go:build ignore或平台限制) - 用绝对路径或相对于模块根目录的路径调用
mockgen,例如:mockgen -source=internal/service/user.go -destination=mocks/mock_user.go - 如果接口在
main包,必须先移到独立包(gomock 不支持 mock main 包) - 使用
-package显式指定生成文件的包名,避免和原包冲突
怎么让 gomock 行为模拟更贴近真实调用链?
关键不在“模拟得多像”,而在“控制得够细”——重点是设置好调用次数、参数匹配、返回值策略。
容易忽略的细节:
- 用
gomock.Any()要谨慎,它会忽略参数校验;生产级测试建议用gomock.Eq(expectedVal)或自定义 matcher - 需要多次调用返回不同值?用
Times(2).Return(val1, nil).Return(val2, errors.New("timeout")) - 想验证某个方法是否被调用但不关心参数?用
EXPECT().Do(func(args ...interface{}) {}).Times(1) - mock 对象生命周期要和 test 函数对齐:在
TestXxx开头调用gomock.NewController(t),它会自动在 test 结束时校验期望是否满足
testify + gomock 组合下,如何避免 test 文件越来越重?
最常滑坡的点:把所有 mock 初始化、fixture 构建、断言逻辑全塞进一个 TestXxx 函数里,导致单测既难读又难改。
轻量组织法:
- 每个
TestXxx只做一件事:调用被测函数 + 核心断言。mock 设置、输入构造、清理逻辑全部提到辅助函数里,如setupMockUserService(t *testing.T) *mock_service.MockUserRepository - 共用 fixture 用
var声明在 test 文件顶部,但注意不要跨 test 共享可变状态(比如 map、slice) - 复杂场景拆
t.Run子测试,每个子测试有自己的gomock.Controller和局部 mock - 别在 test 文件里写业务逻辑判断——所有“if/else 分支覆盖”都应通过不同子测试体现,而不是在单个 test 里 if 判断
真正难的从来不是写 mock,而是决定哪些依赖值得 mock、哪些边界条件必须覆盖、以及当业务逻辑变时,哪些 test 该删而不是硬修。










