Vitest 测试运行生命周期完全指南:从初始化到全局清理的执行链解析

发布时间:2026/9/14 6:33:42

Vitest 测试运行生命周期完全指南:从初始化到全局清理的执行链解析 Vitest 测试运行生命周期完全指南从初始化到全局清理的执行链解析【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitestVitest 从命令行启动到最后一个测试文件结束会经历一条有严格顺序的生命周期流水线初始化配置、全局设置、创建 Worker、执行 setup 文件、收集与运行测试、报告结果、全局清理。本文以 Vitest 官方生命周期文档为主线结合当前仓库的配置文档与源码实现runtime runner、node 主进程完整拆解每个阶段的职责、作用域与执行顺序帮助你写出顺序正确、易于调试、性能更优的测试套件并能准确回答这段代码到底在哪个进程、什么时候执行这类关键问题。生命周期总览七个主阶段一次典型的 Vitest 测试运行会依次经过以下主阶段初始化Initialization加载配置并进行项目级准备全局设置Global Setup在运行任何测试之前执行一次性设置创建 WorkerWorker Creation依据 pool 配置派生出测试 Worker测试文件收集Test File Collection发现并组织测试文件测试执行Test Execution测试与各类 hook、断言按既定顺序运行报告Reporting收集结果并输出报告全局清理Global Teardown所有测试完成后执行最终清理需要特别留意的是阶段 46 会为每个测试文件各自执行一遍。因此对于整个测试套件而言这几个阶段会被反复执行多次当你配置了多于 1 个 Worker 时不同文件的这些阶段还会并行发生。阶段一初始化阶段运行vitest命令后框架首先加载配置并准备测试环境。这一阶段发生的事情解析 命令行参数加载 配置文件校验项目结构触发条件与重复执行当配置文件或其任一被导入的文件发生变化时此阶段会再次执行典型的 watch 模式场景。作用域主进程在任何测试 Worker 创建之前。这意味着初始化阶段做的所有准备都发生在测试代码能够访问任何东西之前——它是整个运行周期中最靠前、也最干净的时机。阶段二全局设置阶段globalSetup如果你配置了globalSetup文件它们会在任何测试 Worker 被创建之前运行一次。这一阶段发生的事情全局设置文件中的setup()函数或导出的default函数按声明顺序依次执行多个全局设置文件按它们在配置中定义的顺序运行作用域主进程与测试 Worker 完全隔离。三条重要注意事项全局设置运行在与测试不同的全局作用域中测试无法访问全局设置中定义的变量如需共享数据请使用provide/inject机制全局设置仅当至少有一个测试排队等待执行时才会运行。典型的全局设置文件如下两种写法等价setup/teardown具名导出或default函数返回 teardownexport function setup(project) { // 在全部测试之前运行一次 console.log(Global setup) // 通过 provide 与测试共享数据 project.provide(apiUrl, http://localhost:3000) } export function teardown() { // 在全部测试之后运行一次 console.log(Global teardown) }深入globalSetup 的配置形态与 provide/inject 数据共享根据 globalSetup 配置文档该选项的类型是string | string[]路径相对于项目 root。setup方法与default函数都会接收一个 TestProject 实例作为第一个参数。多个全局设置文件允许并存setup顺序执行而teardown以逆序执行。由于全局设置与测试运行在不同的全局作用域共享数据必须通过可序列化的provide/inject通道import type { TestProject } from vitest/node export default function setup(project: TestProject) { project.provide(wsPort, 3000) } declare module vitest { export interface ProvidedContext { wsPort: number } }import { inject } from vitest inject(wsPort) 3000provide配置项本身也可直接在vitest.config中静态声明其要求是属性名必须是字符串值必须可被结构化克隆算法序列化因为该对象要在不同进程间传递。若使用 TypeScript可通过declare module vitest增强ProvidedContext接口获得类型安全访问。深入测试重跑rerun时的处理钩子如果你需要为每次测试重跑重新配置全局环境可以使用onTestsRerun钩子——它在 watch 模式下文件变更触发重跑时被调用运行器会等待它完成后才继续执行测试。注意不能以解构方式获取它如{ onTestsRerun }因为它依赖project上下文import type { TestProject } from vitest/node export default function setup(project: TestProject) { project.onTestsRerun(async () { await restartDb() }) }从源码看onTestsRerun回调注册在 node/project.ts 与 node/core.ts 中实现是主进程层面的机制印证了它必须在全局设置作用域内使用的事实。阶段三创建 Worker 阶段全局设置完成后Vitest 根据你的 pool 配置 创建测试 Worker。这一阶段发生的事情根据browser.enabled或pool设置threads、forks、vmThreads或vmForks派生出 Worker每个 Worker 拥有自己的隔离环境除非禁用了 isolation默认情况下 Worker 不会被复用以提供隔离。Worker 仅在下述情形下被复用isolation 被禁用或 pool 为vmThreads/vmForks因为 Node.js VM 本身提供了足够的隔离。作用域Worker 进程/线程。深入四种 pool 的选择pool 配置文档详细对比了四种池的实现差异Pool底层机制特点threadsworker_threads多线程进程相关 API如process.chdir()不可用process.env.TZ修改不影响时区Prisma、bcrypt、canvas 等原生库可能段错误forks默认child_process与主进程通信略慢但进程类 API 可用vmThreadsworker_threads VM 沙箱更快但 ESM 下 VM 模块不稳定、会内存泄漏由 vmMemoryLimit 触发 Worker 重启来对抗Worker 回收昂贵vmForkschild_process VM 沙箱进程类 API 可用回收超限 Worker 只需让子进程退出大套件下通常比vmThreads更快vm 池还要注意沙箱内原生模块抛出的错误其Error构造函数与测试环境中的不同err instanceof Error可能为falseESM 缓存无法清除沙箱中访问全局变量更慢。Worker 数量的上限由 maxWorkers 控制非 watch 模式默认使用全部可用并行度watch 模式默认使用一半支持数字或百分比字符串如50%内部基于os.availableParallelism计算。阶段四测试文件设置阶段setupFiles在每个测试文件运行之前setup 文件 都会被执行。这一阶段发生的事情setup 文件与测试运行在同一个进程中默认情况下 setup 文件并行执行可通过sequence.setupFiles配置为list串行setup 文件在每个测试文件之前执行任何全局状态或配置都可以在此初始化。作用域Worker 进程与测试相同。两条重要注意事项如果 isolation 被禁用setup 文件仍然会在每个测试文件之前重新执行以触发副作用但导入的模块会被缓存——你访问到的是同一个全局对象。因此要避免重复做不必要的工作可用globalThis标志位做幂等保护参见 setupFiles 配置文档 中的示例在 watch 模式下编辑 setup 文件会触发所有测试重跑。一个典型的 setup 文件示例import { afterEach } from vitest // 在每个测试文件之前运行 console.log(Setup file executing) // 注册适用于所有测试的 hook afterEach(() { cleanup() })深入setupFiles 与 globalSetup 的本质区别setupFiles 配置文档 特别强调setup 文件与测试同进程运行、Vitest 会忽略其中的任何导出而globalSetup在主线程中只运行一次。选择依据很简单——如果你需要与测试共享同一进程上下文如注册全局 polyfill、扩展expect用setupFiles如果需要一次性的昂贵准备工作如启动数据库、拉起服务用globalSetup。另外若你在 setup 文件中运行后台重任务可以通过process.env.VITEST_POOL_ID整数字符串区分 Worker 以分摊负载。阶段五测试收集与执行阶段这是测试真正运行的主阶段。测试文件的执行顺序测试文件依据配置执行在单个 Worker 内默认按顺序执行跨不同 Worker 的文件并行运行并行度由maxWorkers配置可通过sequence.shuffle随机化顺序或用sequence.sequencer自定义排序除非启用 shuffle长时间运行的测试通常会先启动基于缓存排序。这是因为 Vitest 用缓存记录各文件的运行耗时让慢文件优先调度从而缩短总耗时而开启 shuffle 后你会失去这一性能收益但能暴露测试间意外的前后依赖。单个测试文件内部的执行顺序每个测试文件的执行严格遵循以下顺序文件级代码describe块之外的所有代码立即执行收集阶段测试收集describe块被处理测试作为导入测试文件的副作用被注册aroundAllhooks包裹套件内的所有测试必须调用runSuite()beforeAllhooks在套件内任何测试之前运行一次每个测试依次执行aroundEachhooks 包裹测试必须调用runTest()beforeEachhooks 执行按定义顺序或依sequence.hooks配置测试函数执行afterEachhooks 执行默认sequence.hooks: stack时按逆序beforeEachhook 返回的清理函数执行默认stack时同样逆序onTestFinished回调运行始终逆序若测试失败onTestFailed回调运行注意若设置了repeats或retry上述所有步骤会重复执行afterAllhooks套件内所有测试完成后运行一次beforeAllhook 返回的清理函数套件内所有测试完成后运行一次。完整执行流程示例// 收集阶段立即执行 console.log(File loaded) describe(User API, () { // 收集阶段立即执行 console.log(Suite defined) aroundAll(async (runSuite) { // 包裹本套件所有测试 console.log(aroundAll before) await runSuite() console.log(aroundAll after) }) beforeAll(() { // 在本套件所有测试之前运行一次 console.log(beforeAll) return function beforeAllCleanup() { // 在 afterAll hooks 之后运行一次 console.log(beforeAllCleanup) } }) aroundEach(async (runTest) { // 包裹每个测试 console.log(aroundEach before) await runTest() console.log(aroundEach after) }) beforeEach(() { // 在每个测试之前运行 console.log(beforeEach) return function beforeEachCleanup() { // 在 afterEach hooks 之后运行 console.log(beforeEachCleanup) } }) test(creates user, () { // 测试执行 console.log(test 1) }) test(updates user, () { // 测试执行 console.log(test 2) }) afterEach(() { // 在每个测试之后运行 console.log(afterEach) }) afterAll(() { // 在本套件所有测试之后运行一次 console.log(afterAll) }) }) // 输出顺序 // File loaded // Suite defined // aroundAll before // beforeAll // aroundEach before // beforeEach // test 1 // afterEach // beforeEachCleanup // aroundEach after // aroundEach before // beforeEach // test 2 // afterEach // beforeEachCleanup // aroundEach after // afterAll // beforeAllCleanup // aroundAll after深入hook 的嵌套与清理链从 Hooks API 可以提炼出几个容易被忽视的细节beforeEach返回的清理函数与afterEach等价唯一区别是它在所有其他afterEach之后执行beforeAll返回的清理函数同理在所有其他afterAll之后执行多个aroundEach/aroundAll互相嵌套先注册的在外层。例如两个aroundEach的输出为outer before → inner before → test → inner after → outer afteraroundEach/aroundAll必须调用runTest()/runSuite()否则测试会报错失败aroundAll未调用时整个套件的测试会被跳过aroundEach适合需要把测试包在某个上下文中执行的场景AsyncLocalStorage 上下文、tracing span、数据库事务等。aroundEach还能拿到测试上下文作为第二个参数因此可以与test.extend的 fixtures 配合使用参考 Hooks API 中的 fixtures 示例所有 hook 的超时默认 10 秒浏览器环境为 30 秒可通过hookTimeout全局配置0表示禁用超时或在单个 hook 上以第二个参数传入毫秒数。嵌套套件Nested Suites使用嵌套describe块时hook 遵循层级模式aroundAll与aroundEach分别包裹各自作用域父级 hook 包裹子级 hookdescribe(outer, () { aroundAll(async (runSuite) { console.log(outer aroundAll before) await runSuite() console.log(outer aroundAll after) }) beforeAll(() console.log(outer beforeAll)) aroundEach(async (runTest) { console.log(outer aroundEach before) await runTest() console.log(outer aroundEach after) }) beforeEach(() console.log(outer beforeEach)) test(outer test, () console.log(outer test)) describe(inner, () { aroundAll(async (runSuite) { console.log(inner aroundAll before) await runSuite() console.log(inner aroundAll after) }) beforeAll(() console.log(inner beforeAll)) aroundEach(async (runTest) { console.log(inner aroundEach before) await runTest() console.log(inner aroundEach after) }) beforeEach(() console.log(inner beforeEach)) test(inner test, () console.log(inner test)) afterEach(() console.log(inner afterEach)) afterAll(() console.log(inner afterAll)) }) afterEach(() console.log(outer afterEach)) afterAll(() console.log(outer afterAll)) }) // 输出顺序 // outer aroundAll before // outer beforeAll // outer aroundEach before // outer beforeEach // outer test // outer afterEach // outer aroundEach after // inner aroundAll before // inner beforeAll // outer aroundEach before // inner aroundEach before // outer beforeEach // inner beforeEach // inner test // inner afterEach // outer afterEach // inner aroundEach after // outer aroundEach after // inner afterAll // inner aroundAll after // outer afterAll // outer aroundAll after可以看到清晰的洋葱模型before系列由外向内逐层深入after系列由内向外逐层退出。这也是新手最容易犯错的区域——把 hook 写错层级就会导致清理顺序与预期不符。并发测试Concurrent Tests使用test.concurrent或sequence.concurrent时同一文件内的测试可以并行运行每个并发测试仍然独立运行自己的beforeEach与afterEachhooks并发快照场景请使用 测试上下文 中的expecttest.concurrent(name, async ({ expect }) {})。关于并发还有一个实践要点由于 Vitest 的全局 hook 不追踪并发测试在test.concurrent中应始终使用测试上下文解构出的onTestFinished/onTestFailed进行资源清理与失败调试而不是全局导入的版本参见 Hooks API 中的并发警示。阶段六报告阶段在整个测试运行期间reporter 接收生命周期事件并展示结果。这一阶段发生的事情reporter 随测试推进接收事件结果被收集并格式化生成测试摘要生成覆盖率报告若启用。关于 reporter 生命周期事件的细节参见 Reporters 指南。从实现上看reporter 的各个回调如onTestRunStart、onTestRunEnd对应着生命周期中不同时点的事件流这也是自定义 reporter 时判断哪个阶段该做什么的依据。阶段七全局清理阶段所有测试完成后全局清理函数执行。这一阶段发生的事情globalSetup文件中的teardown()函数运行多个清理函数按 setup 的逆序运行在 watch 模式下清理在进程退出前执行而不是在每次测试重跑之间。作用域主进程。export function teardown() { // 清理全局资源 console.log(Global teardown complete) }生命周期在不同作用域中的分布理解代码在哪里执行是避免常见坑的关键。下表汇总了各阶段的作用域、测试上下文访问权与运行次数阶段作用域可访问测试上下文运行次数配置文件主进程❌ 否每次 Vitest 运行一次全局设置主进程❌ 否用provide/inject每次 Vitest 运行一次Setup 文件Worker与测试相同✅ 是每个测试文件之前文件级代码Worker✅ 是每个测试文件一次aroundAllWorker✅ 是每个套件一次包裹所有测试beforeAll/afterAllWorker✅ 是每个套件一次aroundEachWorker✅ 是每个测试包裹每个测试beforeEach/afterEachWorker✅ 是每个测试测试函数Worker✅ 是一次retry/repeats 时多次全局清理主进程❌ 否每次 Vitest 运行一次这个表格揭示了核心设计凡是在主进程运行的代码都与测试隔离凡是在 Worker 运行的代码才能与测试共享状态。忘记这一点往往会导致变量在全局设置里定义了但测试里取不到的困惑。Watch 模式下的生命周期在 watch 模式下生命周期会循环执行但有几处差异首次运行执行完整的上述生命周期文件变更时开始新一轮 test run仅重跑受影响的测试文件这些测试文件的 setup 文件 会再次运行globalSetup不会重新执行重跑相关逻辑请使用project.onTestsRerun退出时全局清理执行进程终止。理解这一点对调试很有价值如果你在globalSetup里启动了数据库/服务并期望每次重跑都重置状态watch 模式下它不会重跑——正确做法是把重置逻辑放进onTestsRerun回调。生命周期视角下的性能优化理解生命周期有助于优化测试性能globalSetup适合昂贵的一次性操作数据库播种、服务启动——它只运行一次分摊成本最低setup 文件在每个测试文件之前运行——如果你有大量测试文件避免在其中做重操作beforeAll优于beforeEach适用于不需要隔离的昂贵准备beforeEach在每个测试前都跑一遍禁用 isolation可以提升性能但注意 setup 文件仍然会在每个文件之前执行Pool 配置影响并行化程度与可用的进程/线程 API如threads下无法使用process.chdir()。从仓库的示例项目也可以看到生命周期在实践中的组合使用examples/basic/test/basic.test.ts 与 examples/basic/test/suite.test.ts 展示了文件内 hook 与断言的基本编排而 examples/profiling/global-setup.ts 则展示了全局设置阶段的实战用法。更多优化建议可阅读 Improving Performance 指南。总结Vitest 的生命周期可以概括为一条主进程打底、Worker 反复、主进程收尾的执行链初始化与全局设置在主进程完成一次性准备setup 文件、文件级代码与各类 hook 在Worker内围绕每个测试文件循环执行报告与全局清理再回到主进程收束。把握住作用域与顺序两个维度——aroundAll/aroundEach包裹、before系列由外向内、after系列由内向外、onTestFinished始终逆序——你就能准确预测任何测试文件的执行轨迹写出既高效又不易踩坑的测试套件。相关文档导航Global Setup 配置Setup Files 配置测试排序选项 sequence隔离配置 isolatePool 配置Hooks API 参考 —— 各 hook 的签名与行为provide/inject 数据共享Setup and Teardown 教程 —— hook 的实战入门扩展 Reporter —— reporter 生命周期事件【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/14 6:33:42

学术论文降重工具全解析:原理、评测与实战策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 6:28:42

游戏出海买量成本高?AI语言引擎如何破解本地化瓶颈

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 6:28:42

大模型隐私数据删除技术:PrivacyScalpel原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 7:23:44

用状态机、TDD和上下文管理写出高质量需求文档

需求文档这件事,很多团队其实一直没想明白。你以为需求文档就是“把用户想要的东西写清楚”,那只是及格线。真正能把需求文档写出价值的人,写的是“需求背后的行为逻辑和判定规则”,是让开发、测试、产品三方能对着同一份文档&…

2026/9/14 7:23:44

C语言入门指南:从环境搭建到项目实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 7:23:44

PRD转可点击HTML:零代码实现交互式需求文档

1. 项目概述:为什么一份PRD文档值得被“点开”? “产品经理神器:PRD 直接变可点击网页!”——这句话不是营销话术,而是我过去三年在三个不同规模团队里反复验证过的真实工作流。它解决的不是“能不能做”的技术问题&am…

2026/9/14 7:23:44

MySQL从安装到性能优化:索引、事务、备份与面试核心知识点全梳理

最近接了一个活儿,帮朋友公司梳理一台跑了三年多、几乎没有文档的MySQL数据库服务器。打开命令行敲了几条命令之后,我对着屏幕愣了半天——表结构命名混乱、索引冗余严重、备份策略全靠运气,最要命的是,负责这台库的人换了两茬&am…

2026/9/14 7:23:44

手搓教程:建立技术直觉的硬核学习法

1. 这不是怀旧,是工程师的肌肉记忆在说话 “为什么现在 AI 这么发达了,还要坚持手搓教程?”——这句话最近在技术社区、教学群、甚至新手训练营里反复刷屏。它表面像一句吐槽,实则戳中了当前技术学习生态里最真实的一道裂痕&#…

2026/9/14 7:18:44

STM8S103K3实战:从最小系统到双工具链开发全解析

简介:面向STM8S103K3单片机开发者的完整资料包,以最小系统板PDF原理图、IAR/STVD可运行例程和STM8官方标准外设库为核心,兼顾入门学习与项目参考需求,适合电子专业学生、嵌入式初学者及工程师用作设计蓝本。压缩包共143.57MB&…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/13 11:18:28

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码