当前位置:首页 > 文章列表 > Golang > Go问答 > 并行测试

并行测试

来源:17golang原创 2026-11-11 17:10:19 0浏览 收藏

Go 并行测试共用临时目录时,冲突通常来自多个用例写了同一个文件,或父函数用 defer 提前删除了目录。最稳妥的处理是让每个并行子测试调用自己的 t.TempDir(),把临时路径作为参数传给被测函数;确实需要共享时,再明确只读部分、可写部分和清理责任。

下面用两个生成 result.txt 的小用例说明问题,不扩展到通用并发性能优化。

官方文档:https://pkg.go.dev/testing

先把问题缩成两个写文件的子测试

假设 north 和 south 都把结果写进同一个 root/result.txt。即使用例的数据完全不同,只要路径相同,后写入者就可能覆盖前一个用例的数据,读回断言也可能看见对方的内容。这是文件资源共享问题,并不是临时目录名字“看起来相同”就一定有问题。

t.Parallel() 让子测试参与并行调度;它不会复制文件、隔离全局变量或自动替换路径。尤其要注意:并行子测试会等待父测试函数返回后继续运行,因此父函数返回时执行的 defer 不能当作整组子测试结束后的清理。

为每个子测试分配自己的目录

在已有 Go 模块中新建 sandbox_test.go;包名按项目实际情况修改。以下是完整的独立示例,代码未在本文制作过程中执行。

package sandbox

import (
    "os"
    "path/filepath"
    "testing"
)

func TestParallelFiles(t *testing.T) {
    cases := []struct {
        name string
        data string
    }{
        {name: "north", data: "north-value"},
        {name: "south", data: "south-value"},
    }

    for _, item := range cases {
        tc := item // 显式绑定当前用例,兼容旧项目的循环变量语义。
        t.Run(tc.name, func(t *testing.T) {
            t.Parallel()
            dir := t.TempDir() // 每个子测试申请独立目录,并由 testing 管理清理。
            path := filepath.Join(dir, "result.txt")
            if err := os.WriteFile(path, []byte(tc.data), 0600); err != nil {
                t.Fatalf("写文件失败: %v", err)
            }
            got, err := os.ReadFile(path)
            if err != nil {
                t.Fatalf("读文件失败: %v", err)
            }
            // 不依赖兄弟测试的执行顺序,只核对自己写入的数据。
            if string(got) != tc.data {
                t.Fatalf("内容不匹配: got=%q want=%q", got, tc.data)
            }
        })
    }
}

这里两个文件都叫 result.txt,但所在目录不同。每个子测试只接触自己的路径,所以不需要为文件名本身增加互斥锁。将真实业务接进来时,把 dir 传给文件生成函数,不要让函数偷偷读取包级固定路径。

north与south子测试分别拥有目录和结果文件
图1:独立目录结构说明图。两个用例的同名文件属于不同目录,连线表示资源归属,不表示执行顺序。

共享父目录时明确文件归属

有些集成测试需要公共根目录,便于统一管理。根目录可以由父测试创建,但每个写入用例仍应使用独立子目录。下面的代码与前例使用相同的导入项。

func TestSharedRoot(t *testing.T) {
    root := t.TempDir() // 父测试拥有公共根目录;不手动提前删除。
    for _, name := range []string{"north", "south"} {
        caseName := name
        t.Run(caseName, func(t *testing.T) {
            t.Parallel()
            // 两个用例的名字在此固定集合中不同,且只包含简单目录名。
            dir := filepath.Join(root, caseName)
            if err := os.Mkdir(dir, 0700); err != nil {
                t.Fatalf("创建用例目录失败: %v", err)
            }
            if err := os.WriteFile(filepath.Join(dir, "result.txt"), []byte(caseName), 0600); err != nil {
                t.Fatalf("写入失败: %v", err)
            }
        })
    }
}

这个例子只对固定的简单用例名创建子目录。如果用例名来自外部数据,不能直接拿它拼文件路径:重复名、分隔符和上级目录片段都需要另行处理。更简单的选择仍是每个子测试自行申请 TempDir。

资源并行用例如何使用
每个用例的输出文件放入独立目录,互不覆盖
公共输入样本保持只读,不让用例就地改写
必须共同修改的同一文件按业务语义协调完整读写操作,或将该组改为串行

只给 WriteFile 加锁,而把随后依赖它的 ReadFile 留在锁外,仍可能读到另一个用例的覆盖结果。应先判断两个用例究竟能否独立;隔离输出通常比扩大锁范围更清楚。

把清理绑定到真正的资源拥有者

父测试申请的 TempDir 会保留到它及所有子测试完成,随后自动删除,不需要再写 defer os.RemoveAll(root)。若目录由 os.MkdirTemp 手动创建,则应在拥有它的测试上注册 t.Cleanup,并处理创建和删除错误。

Cleanup 解决的是资源寿命,不负责用例之间的数据隔离。即使目录一直存在,两个并行子测试写同一个文件,仍然会互相影响。反过来,目录完全独立,但清理责任绑定错误,也可能产生文件不存在的失败。

父测试管理共享根目录和清理责任,子测试分别拥有写入区域
图2:资源拥有者结构说明图。父测试负责公共根目录的寿命,north 和 south 分别负责自己的输出区域;清理关系不等于写入同步。

此外,测试函数自己启动的后台 goroutine 必须自行停止并等待结束;TempDir 的自动清理并不意味着任意后台任务都会被等待。否则测试返回后仍在写文件,也会与清理发生冲突。

将示例接入项目并判断结果

先把例子放进已有模块,再将文件写入逻辑替换成项目实际函数。可以使用下面的命令反复运行指定用例;这些是供读者执行的命令,不是本文运行记录。

# 在模块目录中重复运行目标测试,增加观察偶发干扰的机会。
go test -run '^TestParallelFiles$' -count=20 .
# 支持竞态检测的环境可补查内存共享;它不能保证发现文件互相覆盖。
go test -race -run '^TestParallelFiles$' -count=5 .

判断重点是每个用例读回自己的内容、错误信息能对应具体子测试,以及项目代码确实使用传入目录。重复运行没有报错只是一次观察,不能代替路径归属分析。降低并行度后暂时不报错,也不能证明共享写入已经安全。

几个容易混淆的问题

同名文件一定冲突吗?

不一定。关键是实际访问路径是否落在同一个文件上。独立 TempDir 内的同名文件可以分别使用。

父测试的 TempDir 能给子测试用吗?

可以。应让父测试承担寿命管理,同时为并行写入划分独立区域;共享目录本身不会自动隔离文件内容。

为什么 race 检测没有提示?

文件内容被其他用例覆盖属于资源层面的逻辑干扰,不一定表现为 Go 内存数据竞争。仍需核对文件路径和操作语义。

有关子测试等待规则,另见 Go 官方说明:https://go.dev/blog/subtests。并行测试中先划清“谁写哪里、谁负责清理”,再决定是否需要共享和同步。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
结构化日志值
下一篇
结构化日志值
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    983次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    986次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1105次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1076次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    916次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码