必须定义明确、窄小的接口类型而非 interface{},因为 interface{} 无行为契约,导致运行时 panic 且失去编译期检查;应从调用方出发提取实际使用的方法,命名体现角色,粒度控制在 2–4 个方法内,并确保实现与接口签名(含 context.Context 和接收者类型)完全一致。

直接传 interface{} 不行,必须定义明确、窄小的接口类型,否则测试时会 panic 或编译失败。
为什么不能用 interface{} 代替具体接口
Go 里 interface{} 是空接口,它不约束任何方法。当你把它当依赖传进去,调用方代码里写 repo.FindByID(),编译器不会报错——但运行时直接 panic:interface conversion: interface {} is *MockRepo, not UserRepository。这不是 mock 写得不好,是根本没定义契约。
- 接口的核心作用是「声明行为契约」,
interface{}没有行为,只有类型擦除 - 真实依赖(比如
*sql.DB)不是接口,无法被直接 mock;必须先抽象出它被调用的那几个方法 - 一旦用了
interface{},你就放弃了编译期检查,mock 是否实现全部方法只能靠跑测试才发现
怎么定义一个真正可用的依赖接口
从「谁在调用」出发,只提取被测函数实际用到的方法。比如 handler 里只调了 repo.GetByID() 和 cache.Set(),那就只定义这两个方法,别加 Update() 或 Delete()。
- 接口名体现角色,不是实现:用
UserReader而不是SQLUserRepo - 方法签名必须和真实实现一致:参数含
context.Context,返回值顺序、类型完全匹配 - 粒度控制在 2–4 个方法内;
Reader、Writer、Sender都是好范例 - 定义放在被测包或 shared 包里,别塞进 test 文件——避免“为测而抽”,脱离业务语义
函数参数必须是接口类型,不是 struct 指针
常见错误是写成 func NewService(u *UserRepository)。这等于锁死实现,测试时没法塞 mock。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 正确签名是
func NewService(repo UserReader) *Service,其中UserReader是接口类型 - struct 字段也应存接口:如
repo UserReader,而非repo *SQLUserRepo - 注意接收者类型:如果接口方法是
func (r *Mock) Get(...)(指针接收者),mock 实例必须传指针&mock,传值会不满足接口 - 不要提前抽象:只有一个实现时,先不用 interface;等第二版出现(比如内存 → Redis),再抽出来
测试时怎么注入 mock 实例
用匿名结构体或轻量 struct 控制行为,别在方法里硬编码 if-else 分支。
- 字段驱动响应:
GetReturn *User和GetError error,测试 setup 阶段赋值即可 - 需要多轮不同返回?用切片 + 索引计数器,避免状态污染
- 真实实现(如基于
*sql.DB的 repo)要显式实现该接口,Go 会自动检查是否满足 - 别把 mock 实例复用在多个测试里——每次 new 一个干净实例,防止副作用
最易被忽略的是接收者类型匹配和 context.Context 的一致性:漏掉 context 或混用值/指针接收者,会导致接口不满足,编译通过但运行时报错。这不是配置问题,是契约没对齐。

















