Go 子测试调用 Parallel 后父测试什么时候继续执行
子测试调用 t.Parallel() 后,直接包住它的那次 t.Run 就可以解除阻塞,父测试函数会继续执行后面的代码。此时子测试在 t.Parallel 之后的部分处于暂停状态,通常要等父测试函数返回后才进入并行阶段;不过父测试整体还没有结束,它仍会等待所有子测试完成。
官方地址:https://pkg.go.dev/testing#T.Run
t.Run返回:子测试函数返回,或者子测试调用了t.Parallel。- 父测试函数返回:父函数正文和普通
defer都走完,并行子测试才有机会恢复。 - 父测试完成:父函数已返回,而且它的全部子测试也都完成。
我最初混淆的是 Run 返回和测试完成
第一次读并行子测试时,我很容易把“t.Run 已经返回”理解成“子测试已经跑完”。真正的分界线是:T.Run 会在子测试函数返回,或者子测试调用 t.Parallel 变成并行测试时解除阻塞。后一个条件只表示子测试交出了当前执行位置,不表示它已经完成。
Go 官方还明确说明,父测试只有在全部子测试完成后才算完成。因此父代码会继续、子代码会暂停、父测试会等待,这三个状态可以同时成立。

用最小测试标出三个位置
下面的代码不依赖精确日志时间,只用三个位置帮助判断控制权在哪一侧。子测试先执行到 t.Parallel,随后直接 t.Run 可以返回,父函数继续;子测试中 t.Parallel 后面的断言则要等到父测试函数返回后再恢复。
package parallelparent
import "testing"
func TestParentContinues(t *testing.T) {
t.Log("父测试:调用 Run 之前")
t.Run("child", func(t *testing.T) {
// 这行位于 Parallel 之前,会在 Run 解除阻塞前执行。
t.Log("子测试:调用 Parallel 之前")
t.Parallel()
// 这部分先暂停,等父测试函数返回后再进入并行阶段。
t.Log("子测试:Parallel 之后")
})
// 子测试调用 Parallel 后,父测试可以继续到这里。
t.Log("父测试:Run 已返回,但子测试未必完成")
}
这里最有用的判断不是猜日志会不会紧挨着出现,而是看代码位置:Run 后的父代码不会等待并行子测试的后半段;整个 TestParentContinues 却会等子测试全部完成后才报告最终结果。
普通 defer 可能比并行子测试更早清理资源
这个语义最容易在共享资源上踩坑。父函数返回时会执行它自己的普通 defer,而直接子测试在 t.Parallel 之后还没有恢复。如果父函数用 defer 关闭数据库、HTTP 测试服务或临时依赖,子测试恢复后可能面对已经被关闭的资源。
我更倾向于把“测试树结束后再清理”的动作交给 t.Cleanup。官方契约是:Cleanup 注册的函数会在当前测试及其全部子测试完成后调用。
package parallelparent
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestSharedServer(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 测试服务只返回稳定状态,避免共享可变业务数据。
w.WriteHeader(http.StatusNoContent)
}))
// Cleanup 会等当前测试及全部子测试完成后再关闭服务。
t.Cleanup(server.Close)
for _, name := range []string{"first", "second"} {
name := name // 兼容旧版本 Go 的循环变量捕获方式。
t.Run(name, func(t *testing.T) {
t.Parallel()
// 每个子测试创建自己的请求,只有服务地址被共享。
req, err := http.NewRequest(http.MethodGet, server.URL, nil)
if err != nil {
t.Fatal(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close() // 响应体只属于当前子测试。
if resp.StatusCode != http.StatusNoContent {
t.Fatalf("状态码 = %d,期望 %d", resp.StatusCode, http.StatusNoContent)
}
})
}
}

需要明确等待一组子测试时,加一层顺序分组
有时清理动作必须写在一段普通代码里,而不是注册成 Cleanup。此时可以增加一个不调用 t.Parallel 的分组子测试,让并行测试成为它的子节点。分组函数返回后,组内并行子测试开始恢复;外层那次 t.Run("parallel-group", ...) 要等组内子测试全部完成才返回,所以后面的清理代码拥有清楚的等待边界。
package parallelparent
import "testing"
func TestGroupedWorkers(t *testing.T) {
resource := openResourceForTest()
t.Run("parallel-group", func(t *testing.T) {
for _, input := range []string{"a", "b", "c"} {
input := input // 为每个子测试固定当前输入。
t.Run(input, func(t *testing.T) {
t.Parallel()
// 组内子测试并行使用同一个只读测试资源。
if err := resource.Check(input); err != nil {
t.Fatal(err)
}
})
}
})
// 分组 Run 返回时,组内并行子测试已经全部完成。
resource.Close()
}
// testResource 仅表示示例中的受控测试资源。
type testResource struct{}
func openResourceForTest() *testResource { return &testResource{} }
func (r *testResource) Check(input string) error {
// 示例只展示父子测试边界,不引入额外业务逻辑。
return nil
}
func (r *testResource) Close() {
// 实际项目可在这里释放连接、文件或临时服务。
}
这个分组方式的价值在于局部:它只等待当前组里的并行子测试,不要求把整个顶层测试都改成串行清理脚本。
表驱动并行测试里我会保留的边界
- Parallel 之前只做局部准备。 不要在这里修改其他并行子测试也会读写的全局状态。
- 共享资源用并发安全或只读设计。
t.Parallel解决的是调度,不会自动给业务对象加锁。 - 测试树级清理优先用 t.Cleanup。 普通
defer只绑定当前函数返回。 - 需要等待一组子测试时用顺序分组。 让外层 Run 的返回成为清楚的组完成边界。
- -parallel 只限制并行测试数量。 它不会改变父子测试的等待语义,也不保证具体调度顺序。
验收时只问这四个问题
| 检查点 | 正确判断 |
|---|---|
| 直接 t.Run 何时返回 | 子函数返回,或子测试调用 t.Parallel |
| Parallel 后的子代码何时恢复 | 父测试函数返回、非并行阶段结束后 |
| 父测试何时算完成 | 父函数返回且全部子测试完成后 |
| 共享资源何时清理 | 用 t.Cleanup 等到测试树完成,或在分组 Run 返回后清理 |
常见问题
父测试在 t.Run 返回后能立刻读取子测试的最终结果吗? 不能这样假设。子测试可能只执行到 t.Parallel 就暂停,后半段尚未完成。
父测试函数返回后,父测试是不是就结束了? 不是。父测试整体还要等待所有子测试完成。
父测试里的 defer 适合关闭并行子测试共用的服务吗? 通常不适合,因为 defer 随父函数返回执行,可能早于并行子测试恢复。优先使用 t.Cleanup 或顺序分组。
把顶层测试也调用 t.Parallel 能解决等待问题吗? 不能。它会改变顶层测试与其他并行测试的关系,但不会取消父测试等待子测试完成的规则。
Go slog.Handler.WithGroup 怎么避免属性组名冲突
- 上一篇
- Go slog.Handler.WithGroup 怎么避免属性组名冲突
- 下一篇
- Go slog.ReplaceAttr 怎么统一脱敏日志字段
-
- Golang · Go问答 | 21分钟前 | go · database/sql · 排错 · Go 连接池 context database/sql QueryContext
- Go QueryContext 取消后连接为什么没有立即回到池中
- 400浏览 收藏
-
- Golang · Go问答 | 1小时前 | 事务 · go · 数据库 · database/sql · 排错 · Go 事务 database/sql sql.DB sql.Tx
- Go 事务里的查询为什么不能再使用原来的 DB 句柄
- 497浏览 收藏
-
- Golang · Go问答 | 1小时前 | 错误处理 · go · SQL · Go database/sql Rows.Err Rows.Scan
- Go rows.Scan 成功后为什么还必须检查 rows.Err
- 267浏览 收藏
-
- Golang · Go问答 | 2小时前 | TLS · 连接池 · Go问答 · 连接复用 http.Transport 会话恢复 ClientSessionCache Go TLS
- Go TLS 会话恢复为什么不能保证复用同一条连接
- 133浏览 收藏
-
- Golang · Go问答 | 2小时前 | go · TLS · 根证书 SystemCertPool Go x509.CertPool SSL_CERT_FILE 容器TLS
- Go x509.CertPool 为什么在不同系统里根证书数量不同
- 130浏览 收藏
-
- Golang · Go问答 | 3小时前 | go · TLS · 网络安全 · VerifyConnection Go InsecureSkipVerify x509.Verify TLS证书校验 证书固定
- Go InsecureSkipVerify 开启后怎么保留自定义证书校验
- 384浏览 收藏
-
- Golang · Go问答 | 4小时前 | TCP · go · Go 长连接 TCP keepalive 应用层心跳
- Go TCP KeepAlive 为什么不能替代应用层心跳
- 468浏览 收藏
-
- Golang · Go问答 | 8小时前 | 网络编程 · Go问答 · DNS 容器 Go net.Resolver LookupHost
- Go Resolver LookupHost 为什么在容器里结果顺序变化
- 223浏览 收藏
-
- Golang · Go问答 | 8小时前 | 网络编程 · Go问答 · Go 网络超时 net.Conn SetDeadline SetReadDeadline SetWriteDeadline
- Go net.Conn 设置 Deadline 后为什么后续读写一直超时
- 412浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go 输入校验怎么把清洗、验证和 JSON Schema 放在一次流程里
- 287浏览 收藏
-
- Golang · Go问答 | 11小时前 | JSON · go ·
- Go 时间做 JSON 往返后为什么丢失单调时钟信息
- 479浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 347次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 409次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 410次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 369次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 192次使用
-
- 一文详解Go语言单元测试的原理与使用
- 2022-12-29 377浏览
-
- Golang 单元测试和基准测试实例详解
- 2022-12-23 275浏览
-
- Go语言自带测试库testing使用教程
- 2023-01-07 467浏览
-
- 一文带你了解Go语言中的单元测试
- 2022-12-27 485浏览
-
- Go单元测试对数据库CRUD进行Mock测试
- 2023-02-25 411浏览
