Vitest TestModule 任务 API 详解:掌握测试模块的标识、状态与诊断信息
2026/9/13 22:58:15 网站建设 项目流程

Vitest TestModule 任务 API 详解:掌握测试模块的标识、状态与诊断信息

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

TestModule是 Vitest 测试任务树(Task Tree)中代表"单个项目中的单个测试文件模块"的核心类,它只在主线程(main thread)中可用,是自定义 Reporter、Test Runner 与各类工具链集成时最常打交道的任务类型之一。本文将基于官方 API 文档并结合 Vitest 仓库源码,完整讲解TestModuletype判别、moduleId/relativeModuleId标识体系、viteEnvironmentstate()meta()diagnostic()logs()toTestSpecification()等全部属性与方法,读完你可以在自定义 Reporter、Runner 或 CLI 工具中准确读取、过滤并重新调度测试模块。

一、TestModule 是什么:主线程中的模块级任务

TestModule类表示单个项目中的单个测试模块(通常对应一个测试文件),并且只存在于主线程。如果你在 Runtime 中处理任务,应使用 Runner API 中对应的运行时任务类型。

在 reported-tasks.ts 源码中,TestModule继承自SuiteImplementation,并声明了public readonly type = 'module'。由于测试任务存在模块(module)、套件(suite)、用例(test)等多种形态,官方推荐通过type属性进行运行时判别:

if (task.type === 'module') { task // TestModule }

与之相对,TestSuite 的type恒为'suite',用例的type恒为'test'

::: warning 继承说明TestModule继承自TestSuite的全部方法与属性(如childrenerrors()ok()optionsfullName等),本文只列出TestModule独有的方法与属性。相关继承能力可参考 TestSuite API。 :::

二、模块标识:moduleId 与 relativeModuleId

moduleId

moduleId是该模块在 ViteModuleGraph中的唯一标识,通常是绝对 UNIX 风格路径(即使在 Windows 上也是)。如果文件不在磁盘上(例如虚拟模块),它可以是虚拟 ID(virtual id)。

'C:/Users/Documents/project/example.test.ts' // ✅ 合法(UNIX 风格) '/Users/mac/project/example.test.ts' // ✅ 合法 'C:\\Users\\Documents\\project\\example.test.ts' // ❌ 非法(反斜杠形式)

从源码实现看,moduleId直接取自底层任务的filepath

// packages/vitest/src/node/reporters/reported-tasks.ts this.moduleId = task.filepath

relativeModuleId

relativeModuleId是相对当前项目根目录的模块 ID,与旧 API 中的task.name完全一致:

'project/example.test.ts' // ✅ 合法 'example.test.ts' // ✅ 合法 'project\\example.test.ts' // ❌ 非法(反斜杠形式)

源码中它直接对应底层任务的name字段:

this.relativeModuleId = task.name

这两个标识符是构建测试文件过滤、去重、缓存与报告输出的基础,例如自定义 Reporter 中常用relativeModuleId展示给用户,而用moduleId与 Vite 模块图对齐。

三、viteEnvironment:模块的 Vite 开发环境

版本要求:Vitest 4.1.0 起正式提供;在 v4.0.15 中以实验性 API 加入。

viteEnvironment是用于转换该测试模块内所有文件的 ViteDevEnvironment实例。它是理解 Vitest 环境隔离机制的关键:每个测试模块都在特定的 Vite 环境中被转换与执行,环境决定了模块可用的插件、解析与转换管线。

源码中的对应定义如下(注意:模块尚未执行时该字段可能为空):

// packages/vitest/src/node/reporters/reported-tasks.ts public readonly viteEnvironment: DevEnvironment | undefined // 构造函数中按环境名从项目环境中解析 if (typeof task.viteEnvironment === 'string') { this.viteEnvironment = project.vite.environments[task.viteEnvironment] }

因此当你需要通过编程方式读取某个模块的转换结果、模块图或依赖关系时,可以借助viteEnvironment拿到底层环境对象。

四、state():查询模块运行状态

function state(): TestModuleState

state()与 TestSuite.state() 工作方式相同,可返回pendingfailedpassedskipped区别在于TestModule还可以返回queued,表示模块尚未被执行(仍在队列中等待调度)。

源码中的TestModuleState类型在TestSuiteState基础上扩展了queued

export type TestSuiteState = 'skipped' | 'pending' | 'failed' | 'passed' export type TestModuleState = TestSuiteState | 'queued'

实现逻辑为:先读取底层任务的result.state,若为queued直接返回,否则复用套件状态计算逻辑:

public state(): TestModuleState { const state = this.task.result?.state if (state === 'queued') { return 'queued' } return getSuiteState(this.task) }

五、meta():读写模块级自定义元数据

版本要求:Vitest 3.1.0 起提供。

function meta(): TaskMeta

返回模块在收集(collection)或执行(execution)期间被附加的自定义元数据。元数据的附加方式是:在测试运行期间直接给task.meta对象赋值属性。官方文档给出如下示例:

import { test } from 'vitest' describe('the validation works correctly', (task) => { // assign "decorated" during collection task.file.meta.decorated = false test('some test', ({ task }) => { // assign "decorated" during test run, it will be available // only in onTestCaseReady hook task.file.meta.decorated = false }) })

::: tip 使用时机提示 如果元数据是在收集阶段test函数之外)附加的,那么它在自定义 Reporter 的onTestModuleCollected钩子中即可读取;若是在测试运行期间附加,则只能在onTestCaseReady钩子之后才可见。 :::

六、diagnostic():模块级性能与资源诊断

function diagnostic(): ModuleDiagnostic

返回模块的有用诊断信息,如耗时、内存占用等。如果模块尚未执行,所有诊断值都会返回0。完整的ModuleDiagnostic接口(与源码 reported-tasks.ts 定义一致)如下:

interface ModuleDiagnostic { /** * 导入并初始化环境所花费的时间。 */ readonly environmentSetupDuration: number /** * Vitest 搭建测试脚手架(runner、mocks 等)所花费的时间。 */ readonly prepareDuration: number /** * 导入测试模块所花费的时间。 * 包含导入模块内所有内容以及执行套件回调。 */ readonly collectDuration: number /** * 导入 setup 模块所花费的时间。 */ readonly setupDuration: number /** * 模块内所有测试与钩子的累计耗时。 */ readonly duration: number /** * 模块占用的内存字节数。 * 仅当使用 `logHeapUsage` 标志执行测试时可用。 */ readonly heap: number | undefined /** * Vitest 处理过的每个非外部化依赖的导入耗时。 */ readonly importDurations: Record<string, ImportDuration> /** * 运行该文件的 worker 的 id。该值不会高于 `maxWorkers`。 * 如果文件尚未运行,该值为 0。 * * 注意:Node.js 测试与浏览器测试运行在不同的 pool 中,不共享 `concurrencyId`, * 因此可能出现多个模块拥有相同 `concurrencyId` 的情况。 * 请使用 `project.isBrowserEnabled()` 加以区分。 */ readonly concurrencyId: number /** * 运行该文件的 worker 的递增编号,随每个 worker 增加。 * 如果文件尚未运行,该值为 0。 * * 注意:Node.js 测试与浏览器测试运行在不同的 pool 中,不共享 `workerId`, * 因此可能出现多个模块拥有相同 `workerId` 的情况。 * 请使用 `project.isBrowserEnabled()` 加以区分。 */ readonly workerId: number } /** 导入并执行某个非外部化文件所花费的时间。 */ interface ImportDuration { /** 导入并执行该文件本身(不计其非外部化导入)的时间。 */ selfTime: number /** 导入并执行该文件及其所有导入的时间。 */ totalTime: number }

各字段在源码diagnostic()实现中的取值来源非常清晰——它们直接映射到底层任务的不同耗时记录:

const setupDuration = this.task.setupDuration || 0 const collectDuration = this.task.collectDuration || 0 const prepareDuration = this.task.prepareDuration || 0 const environmentSetupDuration = this.task.environmentLoad || 0 const duration = this.task.result?.duration || 0 const heap = this.task.result?.heap const importDurations = this.task.importDurations ?? {}

实战建议

  • 分析慢测试模块时,可对比collectDurationduration:前者代表收集开销,后者代表执行开销;
  • heap需要配合 logHeapUsage 配置开启后才会有值;
  • 通过importDurations可以精确定位"哪个依赖导入最耗时"(selfTime是该文件自身耗时,totalTime含其全部传递导入),是排查启动缓慢的得力工具。

七、logs():收集阶段的顶层控制台日志

版本要求:Vitest 5.0.0 起提供。

function logs(): ReadonlyArray<UserConsoleLog>

返回在测试收集期间、模块顶层记录的 console 日志。注意它只包含模块顶层(top level)的输出,不包含套件回调或测试函数内部的输出:

console.log('included') // ✅ 会被记录(模块顶层) describe('suite', () => { console.log('not included') // ❌ 套件回调内 test('test', () => { console.log('not included') // ❌ 测试函数内 }) })

这与 TestSuite.logs()(记录套件及其beforeAll钩子收集期间的日志)形成了"粒度"上的互补:模块级只看文件顶层,套件级覆盖套件作用域。

八、toTestSpecification():生成可执行的测试规格

版本要求:Vitest 4.1.0 起提供。

function toTestSpecification(testCases?: TestCase[]): TestSpecification

返回一个新的测试规格(TestSpecification),可用于过滤或运行这个特定的测试模块。它接受一个可选的测试用例数组,用于限定要运行的用例范围。

源码实现(reported-tasks.ts)展示了其内部逻辑:它会识别该模块是否为 typecheck 模块(meta.typecheck === true),并调用TestProject.createSpecification生成规格:

public toTestSpecification(testCases?: TestCase[]): TestSpecification { const isTypecheck = this.task.meta.typecheck === true return this.project.createSpecification( this.moduleId, testCases?.length ? { testIds: testCases.map(t => t.id) } : undefined, isTypecheck ? 'typecheck' : undefined, ) }

createSpecification(见 project.ts)会结合模块 ID、目标测试用例 ID 与项目 pool 信息,构建出 Vitest 调度器可消费的规格对象:

public createSpecification( moduleId: string, locationsOrOptions?: number[] | TestSpecificationOptions | undefined, pool?: string, taskIdOverride?: string, ): TestSpecification { return new TestSpecification( this, moduleId, pool || getFilePoolName(this), locationsOrOptions, taskIdOverride, ) }

典型用法:在自定义工具或 Reporter 中,若需要"只重跑某个模块/某几个用例",可以用module.toTestSpecification()拿到规格,再交给运行调度层处理;传入testCases数组即可把范围收窄到指定用例(底层按test.id过滤)。

九、小结与源码索引

TestModule是 Vitest 任务体系中最顶层的"文件级"任务,其职责可以概括为三块:

  1. 标识moduleId(Vite 模块图 ID)与relativeModuleId(相对项目路径);
  2. 状态与数据state()(含独有的queued)、meta()(自定义元数据)、diagnostic()(耗时/内存/worker 诊断)、logs()(收集期顶层日志);
  3. 调度toTestSpecification()把模块(或部分用例)包装成可执行的测试规格。

它继承自TestSuite,因此 TestSuite API 中的childrenerrors()optionsprojectmodule等能力同样适用。如需继续深入研究:

  • 类实现与ModuleDiagnostic接口定义:packages/vitest/src/node/reporters/reported-tasks.ts
  • TestSpecification的创建入口:packages/vitest/src/node/project.ts
  • 元数据机制:API 高级 · metadata
  • Runner 侧运行时任务:API 高级 · runner

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询