- Go 的并发特性及测试困难:Go 的特色之一是内置对并发的支持,如 goroutine 和 channel,但测试并发程序困难且易出错。例如测试
context.AfterFunc
函数,需检查函数在上下文取消前后的调用情况,在并发系统中检查未发生的情况困难,常见方法是等待一段时间后判断事件是否发生,这会导致测试缓慢且不稳定。 - testing/synctest 包的引入:Go 1.24 引入新的实验性
testing/synctest
包来支持测试并发代码,该包仅包含Run
和Wait
两个函数。使用该包可重写测试代码,使其简单、快速且可靠,无需修改被测试代码。如测试AfterFunc
函数时,将测试代码包裹在synctest.Run
中,在断言函数调用前后调用synctest.Wait
。Wait
函数等待调用者所在的“气泡”(bubble)中的所有 goroutine 阻塞,返回时可确定上下文包是否调用了函数。该测试比之前的更简单,race 检测器也能正确识别数据竞争。 - 测试时间相关代码:并发代码常处理时间,测试与时间相关的代码困难,使用真实时间会导致测试缓慢且不稳定,使用假时间需避免
time
包函数并设计被测试代码以适应可选的假时钟。testing/synctest
包使测试使用时间的代码更简单,在Run
启动的“气泡”中的 goroutine 使用假时钟,time
包函数在“气泡”中基于假时钟运行,时间在所有 goroutine 阻塞时推进。 - 阻塞与“气泡”:“测试 / synctest”的一个关键概念是“气泡”变得“持久阻塞”,当“气泡”中的每个 goroutine 都被阻塞且只能由“气泡”中的另一个 goroutine 解除阻塞时发生。当“气泡”持久阻塞时,若有未完成的
Wait
调用则返回,否则时间推进到下一个可能解除阻塞 goroutine 的时间,否则“气泡”死锁且Run
panic。不是持久阻塞的情况包括:操作sync.Mutex
、在“气泡”外创建的通道操作、外部 I/O 操作等。 - “气泡”生命周期:
Run
函数在新的“气泡”中启动一个 goroutine,当“气泡”中的每个 goroutine 退出时返回,若“气泡”持久阻塞且无法通过推进时间解除阻塞则 panic。测试时需注意清理背景 goroutine。 - 测试网络代码:以测试
net/http
包处理 100 Continue 响应为例,通常使用环回网络连接的测试可使用testing/synctest
包通过创建内存网络连接来检测所有 goroutine 是否阻塞在网络上。测试过程中启动多个 goroutine,synctest.Run
会等待它们全部退出。该测试可轻松扩展以测试其他行为。 - 实验状态:
testing/synctest
在 Go 1.24 中是实验性包,根据反馈和经验可能会发布、继续实验或删除,默认不可见,使用时需在环境中设置GOEXPERIMENT=synctest
,欢迎反馈测试经验。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。