fake 是轻量可运行的替代实现(如内存版 redis.Client),stub 仅返回预设值且无逻辑;fake 适合模拟状态交互,stub 适合校验调用本身。

Go 测试里 fake 和 stub 不是同一个东西
它们都用来替换真实依赖,但目的和实现粒度不同:fake 是一个轻量、可运行的替代实现(比如内存版 redis.Client),而 stub 只是返回预设值的函数或对象,不包含逻辑。
常见错误是把一个只返回固定值的结构体叫 fake——它其实只是 stub。混淆会导致测试看似通过,实则漏掉边界逻辑(比如重试、超时、状态流转)。
-
stub适合:校验调用是否发生、参数是否正确、返回值是否符合预期(如http.Client返回404或500) -
fake适合:需要模拟内部状态或行为交互的场景(如time.Now()的推进、sync.Mutex的竞争、数据库事务的提交/回滚) - Go 标准库自带的
httptest.Server是fake;而io.NopCloser或一个只返回nil的io.WriteCloser实现,是stub
什么时候该写 fake 而不是 stub
当你发现 stub 开始“记住状态”或者“根据输入分支返回不同结果”,就该升级成 fake了。这不是代码量问题,而是测试意图是否被稀释。
例如测试一个重试逻辑:如果只用 stub 返回三次 error 再返回 nil,那这个“三次”就得硬编码在测试里,耦合严重;换成 fake 后,可以封装成 fakeHTTPClient{attempts: 3},复用性高,也更贴近真实行为。
立即学习“go语言免费学习笔记(深入)”;
- 典型信号:stub 实现里出现
if/switch/ 计数器 / 时间戳判断 - 性能影响:fake 通常比 stub 略重,但只要不涉及 goroutine 泄漏或阻塞,不影响单元测试速度
- 兼容性注意:fake 必须实现完整接口,否则编译失败;stub 可以只实现测试用到的那几个方法(Go 接口很轻)
Go 中 fake 的常见写法与易错点
fake 不是 mock,不需要框架生成,手写更可控。关键在于:它要像真实依赖一样“活”,但又不能有副作用。
比如写一个 fakeClock:
type fakeClock struct {
now time.Time
}
func (f *fakeClock) Now() time.Time { return f.now }
func (f *fakeClock) Advance(d time.Duration) { f.now = f.now.Add(d) }
错误写法是直接传入 time.Time 值并返回——那就只是 stub;必须提供可变状态(如 Advance)才构成 fake。
- 别在 fake 里调用真实系统时间(如
time.Now())、网络、磁盘,否则就失去隔离性 - fake 的字段应尽量公开(如
now),方便测试中检查或修改;避免全靠方法操作 - 如果 fake 需要并发安全(如模拟多 goroutine 操作的 cache),记得加
sync.RWMutex,否则测试可能偶发失败
Stub 在 Go 测试中的实用边界
stub 最适合“断言调用本身”的场景,比如验证某个函数是否被传入了正确的 context、是否带了预期的 header、是否触发了回调。
它往往是一次性的、无状态的,甚至可以是闭包:
var called bool
client := &stubHTTPClient{DoFunc: func(req *http.Request) (*http.Response, error) {
called = true
return &http.Response{StatusCode: 200}, nil
}}
这种写法比定义结构体更快,也更聚焦于“我只关心它被调了没”。但一旦开始在闭包里维护状态(比如计数、记录参数),就滑向 fake 的范畴了。
- stub 的最大风险是“过度简化”:比如用
io.NopCloser(strings.NewReader(""))替代真实响应体,却忘了真实响应体可能有Close()的副作用 - 不要为 stub 写单独的 package,它通常只服务于一两个测试文件;fake 可以抽成
internal/fake复用 - Go 的 interface 天然支持 stub:只要满足方法签名,哪怕是个函数类型(如
type Doer func(*http.Request) (*http.Response, error))也能当 stub 用
真正难的不是区分 fake 和 stub,而是判断“这个依赖的行为,到底有没有状态?有没有时间维度?有没有并发影响?”——问清楚这三个问题,选哪个就自然了。










