t.deadline()仅在go test -timeout显式指定时返回有效时间,否则为零值;需先检查!deadline.iszero()再使用,且它不触发取消,不能替代context.withtimeout。

测试函数里 t.Deadline() 返回零值,根本没用?
因为 t.Deadline() 只在 go test -timeout 显式指定时才返回有效时间;默认不设超时,它就返回 time.Time{}(零值),直接拿它算剩余时间会 panic 或逻辑错。
- 必须先检查
!deadline.IsZero()再使用 - 它只反映
go test命令级超时,和单个t.Run()无关 - 别把它当
context.WithTimeout的替代品——它不触发取消,也不传播信号
deadline, ok := t.Deadline()
if ok {
remaining := time.Until(deadline)
if remaining < 100*time.Millisecond {
t.Skip("too little time left")
}
}
用 context.WithTimeout 包裹被测代码,但测试没提前结束?
常见原因是:被测函数没主动监听 ctx.Done(),或用了不支持 context 的底层调用(比如原生 net.Dial 而非 net.DialContext)。
- 所有 I/O、sleep、channel 操作都得改用带
Context版本(time.Sleep无法取消,要用time.AfterFunc+ctx.Done()配合) - 注意 goroutine 泄漏:启动的子 goroutine 必须在
ctx.Done()后退出,且主协程要wait它们 -
context.WithTimeout的CancelFunc一定要调用,否则 timer 不释放
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() // 必须 defer result, err := doSomethingWithContext(ctx)
同时用 t.Deadline() 和 context.WithDeadline,结果冲突?
会。比如 go test -timeout=1s,你在测试里又写 context.WithDeadline(ctx, time.Now().Add(5*time.Second)),那 context 实际不会超时——它比测试总时限还长,起不到保护作用。
- 推荐做法:用
t.Deadline()算出剩余时间,再传给context.WithTimeout - 避免硬编码超时值,尤其在子测试(
t.Run)中 - 注意时区/单调时钟问题:
time.Now()不如time.Until(deadline)稳定
if deadline, ok := t.Deadline(); ok {
ctx, cancel := context.WithTimeout(context.Background(), time.Until(deadline)/2)
defer cancel()
// 用 ctx 调用被测逻辑
}
为什么 test -timeout 没杀掉卡死的 goroutine?
Go 测试框架只会在测试函数返回后检查超时;如果测试函数本身卡在阻塞调用(如 sync.WaitGroup.Wait()、无缓冲 channel send/receive)里,进程不会退出,-timeout 形同虚设。
立即学习“go语言免费学习笔记(深入)”;
- 必须靠
context主动中断阻塞点,不能依赖外部 kill -
select语句里必须包含ctx.Done()分支,并做清理 - 慎用
runtime.Goexit()或os.Exit()在测试中强行退出——这会跳过 defer,掩盖资源泄漏
最易忽略的一点:http.Client 默认不读取 context,必须显式设置 Timeout 字段或用 DoContext 方法。否则即使传了 context,HTTP 请求照样挂住。










