测试带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 }, 1) go func()>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 数持续上涨。