最直接测试带 context.Context 的函数是用 context.WithTimeout(context.Background(), 1ms) 触发超时分支,避免 sleep 等待或 mock ctx.Done();需确保外部依赖(如 HTTP)使用 WithContext 并检查 select 中 ctx.Done() 是否优先于业务 channel。

用 testify/mock 或原生 context.WithTimeout 模拟超时最直接
测试带 context.Context 的函数,核心不是“mock context”,而是控制它的取消时机。Go 标准库的 context.WithTimeout 和 context.WithCancel 本身可测试——你只需决定 timeout 时间够短(比如 1ms),让代码必走超时分支。
常见错误是写 time.Sleep(50 * time.Millisecond) 等待,这既慢又不稳定;或者试图 mock ctx.Done() 返回的 channel,反而绕过 context 语义,导致测试失真。
实操建议:
- 对被测函数传入
context.WithTimeout(context.Background(), 1*time.Millisecond),再用time.Sleep(2 * time.Millisecond)确保触发超时 - 若函数内部调用了外部依赖(如 HTTP 请求),不要 mock 整个依赖,而是确保它尊重传入的
ctx—— 例如用http.NewRequestWithContext(ctx, ...) - 避免在测试中用
context.TODO()或context.Background()直接传入,它们永远不会超时,无法覆盖 timeout 分支
检查 select 中 ctx.Done() 是否优先于业务 channel
很多 bug 出在 select 语句顺序或漏判 ctx.Err()。比如:
立即学习“go语言免费学习笔记(深入)”;
select {
case res := <-ch:
return res, nil
case <-ctx.Done():
return nil, ctx.Err()
}这段看着没问题,但如果 ch 是个缓冲 channel 且已有值,res := 会立即执行,哪怕 <code>ctx 已超时——违反“超时应中断”的契约。
正确做法是始终先检查 ctx.Err(),或确保 ctx.Done() 在 select 中有同等调度机会:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 把
放在 <code>select第一个 case(不保证绝对优先,但更易暴露竞态) - 在读取业务 channel 前显式调用
if err := ctx.Err(); err != nil { return nil, err } - 用
select+default配合ctx.Select(需第三方库)不如直白判断ctx.Err()可靠
HTTP 客户端超时测试必须传 context 到 http.NewRequestWithContext
只给 http.Client.Timeout 不等于给 request 加 context 超时。前者只控制连接+响应体读取,后者才能中断 DNS 查询、TLS 握手、重定向等全链路。
典型错误写法:
req, _ := http.NewRequest("GET", url, nil)
req = req.WithContext(ctx) // ❌ 错!WithCtx 是值方法,不修改原 req
// 实际没生效正确写法:
- 用
http.NewRequestWithContext(ctx, method, url, body)构造请求(推荐) - 或用
req = req.Clone(ctx)替换 context(注意:clone 不复制 body reader,需自行处理) - 测试时仍要用短 timeout context,并验证返回 error 是否为
context.DeadlineExceeded或context.Canceled
并发场景下多个 goroutine 共享同一 ctx 时,要验证 cancel 传播是否及时
比如一个函数启动了 3 个子 goroutine 并共享同一个 ctx,测试不能只看主函数返回,还得确认所有 goroutine 是否真正退出——否则可能泄漏 goroutine。
简单验证方式:
- 在每个 goroutine 开头加
defer fmt.Printf("goroutine %d done\n", id),配合t.Log观察输出顺序和数量 - 用
runtime.NumGoroutine()在 cancel 前后打点(仅限单元测试,非生产) - 更可靠的是在 goroutine 内部监听
ctx.Done()后执行明确的 cleanup(如关闭 channel、释放锁),并在测试中 assert 这些状态
容易忽略的一点:如果某个 goroutine 在 ctx.Done() 后还阻塞在系统调用(如 time.Sleep、无缓冲 channel 发送),它不会立刻响应 cancel——得靠 select 主动轮询或改用可中断的原语(如 time.AfterFunc 配合 flag)。

















