最直接测试带 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()。比如:
select {
case res := <p>这段看着没问题,但如果 <code>ch</code> 是个缓冲 channel 且已有值,<code>res := 会立即执行,哪怕 <code>ctx</code> 已超时——违反“超时应中断”的契约。</code></p><p>正确做法是始终先检查 <code>ctx.Err()</code>,或确保 <code>ctx.Done()</code> 在 <code>select</code> 中有同等调度机会:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8661" title="Golang Uber Dig"><img
src="https://img.php.cn/upload/skill/000/000/081/179215391842073.jpg" alt="Golang Uber Dig" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8661" title="Golang Uber Dig" class="overflowclass">Golang Uber Dig</a>
<p class="overflowclass">使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等</p>
</div>
<a rel="nofollow" href="/xiazai/skill8661" title="Golang Uber Dig" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 把
放在 <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)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










