当前位置:首页 > 文章列表 > Golang > Go问答 > Go 子测试调用 Parallel 后父测试什么时候继续执行

Go 子测试调用 Parallel 后父测试什么时候继续执行

来源:17golang原创 2026-10-06 13:47:38 0浏览 收藏

子测试调用 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 官方还明确说明,父测试只有在全部子测试完成后才算完成。因此父代码会继续、子代码会暂停、父测试会等待,这三个状态可以同时成立。

Go 父测试、t.Run、t.Parallel 与并行子测试组的静态边界结构图
图1:父测试边界、子测试边界与调度屏障的静态结构说明图,不是测试运行截图。

用最小测试标出三个位置

下面的代码不依赖精确日志时间,只用三个位置帮助判断控制权在哪一侧。子测试先执行到 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)
            }
        })
    }
}
Go 并行子测试中 defer、t.Cleanup 与分组 t.Run 的资源清理关系图
图2:普通 defer、t.Cleanup 和并行子测试完成条件的静态关系说明图,不是运行结果。

需要明确等待一组子测试时,加一层顺序分组

有时清理动作必须写在一段普通代码里,而不是注册成 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 能解决等待问题吗? 不能。它会改变顶层测试与其他并行测试的关系,但不会取消父测试等待子测试完成的规则。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go slog.Handler.WithGroup 怎么避免属性组名冲突Go slog.Handler.WithGroup 怎么避免属性组名冲突
上一篇
Go slog.Handler.WithGroup 怎么避免属性组名冲突
Go slog.ReplaceAttr 怎么统一脱敏日志字段
下一篇
Go slog.ReplaceAttr 怎么统一脱敏日志字段
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    347次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    409次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    410次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    369次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    192次使用