测试带goroutine的函数时总提前结束,是因为主goroutine执行完即退出,未等待异步逻辑完成;应使用sync.WaitGroup或channel同步,避免sleep和全局状态,并用goleak检测goroutine泄漏。

测试带 goroutine 的函数时,为什么测试总提前结束?
因为 go 关键字启动的 goroutine 是非阻塞的,主 goroutine(即测试函数)执行完就退出,根本等不到异步逻辑完成。常见现象是:测试通过但实际逻辑没跑、日志没打印、断言全被跳过。
核心解决思路只有一个:让测试 goroutine 主动等待异步任务结束。不能靠 time.Sleep 硬等——它不可靠、拖慢测试、还可能在 CI 上因机器负载失败。
- 用
sync.WaitGroup记录并等待 goroutine 完成 - 用
channel接收完成信号(更灵活,适合带返回值或错误的场景) - 避免在测试中直接操作全局状态或共享变量,否则并发下易出竞态
用 WaitGroup 测试无返回值的异步函数
适用于类似 “发日志”“上报指标” 这类只做副作用、不关心结果的函数。关键点是:WaitGroup 必须在 goroutine 启动前 Add(1),且 Done() 必须在 goroutine 内部调用(不能在外部代劳)。
func TestAsyncLog(t *testing.T) {
var wg sync.WaitGroup
logs := make([]string, 0)
// 模拟异步写日志函数
asyncLog := func(msg string) {
wg.Add(1)
go func() {
defer wg.Done()
logs = append(logs, msg)
}()
}
asyncLog("started")
asyncLog("finished")
wg.Wait() // 阻塞直到所有 goroutine 调用 Done()
if len(logs) != 2 {
t.Fatalf("expected 2 logs, got %d", len(logs))
}}
立即学习“go语言免费学习笔记(深入)”;
⚠️ 容易踩的坑:wg.Add(1) 写在 go func() 外面但位置不对(比如写在 goroutine 内部),会导致计数漏加或 panic;wg.Wait() 放错位置(如放在两次 asyncLog 中间),会提前阻塞。
用 channel 测试有返回/错误的异步函数
当异步函数需要返回结果或错误(例如 HTTP 请求、数据库查询封装),channel 是更自然的选择。测试代码通过接收 channel 数据来同步,并可直接断言返回值。
示例中 fetchData 启动 goroutine 执行耗时操作,通过 done channel 发送结果:
func fetchData(url string) <-chan struct{ data string; err error } {
ch := make(chan struct{ data string; err error }, 1)
go func() {
// 模拟异步请求
time.Sleep(10 * time.Millisecond)
ch <- struct{ data string; err error }{"ok", nil}
}()
return ch
}
func TestFetchData(t *testing.T) {
ch := fetchData("https://www.php.cn/link/b05edd78c294dcf6d960190bf5bde635")
select {
case result := <-ch:
if result.err != nil {
t.Fatal(result.err)
}
if result.data != "ok" {
t.Errorf("expected 'ok', got %q", result.data)
}
case <-time.After(100 * time.Millisecond):
t.Fatal("timeout: fetchData did not return")
}}
立即学习“go语言免费学习笔记(深入)”;
注意:select + time.After 是必须的,防止 channel 永久阻塞导致测试卡死;channel 缓冲大小设为 1 可避免 goroutine 泄漏(如果测试提前失败,未读 channel 仍能写入一次)。
如何检测 goroutine 泄漏和竞态?
Go 测试本身不报 goroutine 泄漏,但可通过 -race 标志发现数据竞争,用 runtime.NumGoroutine() 做粗略检查(仅限简单场景)。
- 运行
go test -race,任何共享变量被多 goroutine 无保护读写都会报错 - 在测试前后记录 goroutine 数量:
before := runtime.NumGoroutine()→ 执行异步逻辑 →after := runtime.NumGoroutine(),若after > before + 1(+1 是当前测试 goroutine),大概率存在泄漏 - 真正可靠的泄漏检测需用
pprof或第三方库如github.com/uber-go/goleak,它能在测试结束时自动扫描残留 goroutine
goleak 最简用法:在 TestMain 中启用,所有测试自动受检。漏掉它,你可能上线后才看到 goroutine 数持续上涨。










