绝大多数 Go 项目应优先手写 fake,仅当 mock 外部模块(如 cloud.google.com/go/storage.Client)或接口方法超 10 个且需严格校验调用顺序时才用 gomock。

绝大多数 Go 项目里,手写 fake 比用 gomock 更快、更稳、更容易调试;只有当你 mock 的是外部模块(比如 cloud.google.com/go/storage.Client)或接口方法超 10 个且调用顺序必须严格校验时,才值得引入 mockgen + gomock。
什么时候该手写 fake 而不是跑 mockgen
手写 fake 是默认选项,不是“偷懒”。它天然规避了生成代码带来的维护负担和隐式耦合。
- 你定义的
UserRepository、Notifier、PaymentService等内部接口,只实现测试中真正调用的方法即可,不用管接口里其他“理论上存在”的方法 - 字段可导出(如
Called bool、LastID int),测试里直接断言:assert.True(t, fakeRepo.Called),不依赖黑盒式的ctrl.Finish()或mock.AssertExpectations(t) - 要模拟并发?直接嵌入
sync.Mutex,在方法里加mu.Lock(),比学Times(3)和After()直观得多 -
mockgen生成一堆代码却只用其中 1 个方法,是典型信号:该换手写 fake 了
HTTP 客户端测试别碰 http.DefaultClient
硬编码 http.Get 或直接 new(http.Client) 是 mock 失败的根源——它让测试强依赖网络、第三方服务状态,CI 上随机超时、404、SSL 错误全来了。
- 把请求入口抽成可配置字段:比如
type UserClient struct { BaseURL string; Client *http.Client } - 测试中启动
httptest.Server:server := httptest.NewServer(handler); defer server.Close() - 务必传
server.URL给被测 client,不是拼接字符串(避免路径不一致) - handler 中用
json.NewEncoder(w).Encode()返回响应,别用 raw string(否则Content-Type缺失导致解析失败) - 若被测代码已固定用
http.DefaultClient且无法改,才考虑httpmock;但它对自定义Transport无效,且不报错——这是最隐蔽的坑
sqlmock 不是 mock *sql.DB,而是 mock driver.Conn 行为
*sql.DB 是不可导出字段的高度封装结构体,无法直接赋值 mock 实例;sqlmock 的本质是返回一个符合 database/sql 语义的 fake *sql.DB,它 mock 的是底层驱动交互。
立即学习“go语言免费学习笔记(深入)”;
- 必须先定义仓储接口(如
type UserRepo interface { GetUserByID(id int) (*User, error) }),再让实现接收*sql.DB作为依赖 - 测试中用
sqlmock.New()创建 mock DB,注入到被测对象(不是替换*sql.DB字段本身) - 预设期望时注意 SQL 匹配粒度:
mock.ExpectQuery("SELECT.*FROM users")比写死整条 SQL 更健壮 - 事务需显式跟踪:
mock.ExpectBegin()→tx.QueryRow(...)→mock.ExpectCommit();漏掉任一环都会 panic 或静默失败 - SQL 太动态(列名拼接、CTE)、涉及触发器或需要真实约束校验时,
sqlite://:memory:往往比sqlmock更务实
gomock 只在特定场景下值得投入
它不是“高级”或“正确”的代名词,而是一个有明确适用边界的工具。滥用反而增加复杂度。
- 接口来自外部 module(如
google.golang.org/api/option.ClientOption),且它没提供可替换的 interface —— 这是gomock最正当的使用理由 - 接口方法超过 10 个、签名频繁变更、且调用顺序/参数匹配必须严格校验(比如支付网关回调链路)
- 改了接口签名后忘记重跑
mockgen,会导致编译失败或运行时 panic(因为生成代码仍按旧签名实现) -
mockgen默认生成非导出方法(首字母小写),如果源接口方法名小写,会生成失败;务必确保接口定义在可导出的 .go 文件中,且方法名大写
真正容易被忽略的是:mock 的目标从来不是“看起来像接口”,而是“让业务逻辑能被确定性地触发和验证”。手写 fake 把控制权交还给开发者,而 gomock 把控制权交给生成器和控制器——选哪个,取决于你愿不愿意为那点“自动”多承担一层抽象和维护成本。


















