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 仓库源码,完整讲解TestModule的type判别、moduleId/relativeModuleId标识体系、viteEnvironment、state()、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的全部方法与属性(如children、errors()、ok()、options、fullName等),本文只列出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.filepathrelativeModuleId
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(): TestModuleStatestate()与 TestSuite.state() 工作方式相同,可返回pending、failed、passed、skipped;区别在于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 ?? {}实战建议:
- 分析慢测试模块时,可对比
collectDuration与duration:前者代表收集开销,后者代表执行开销; 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 任务体系中最顶层的"文件级"任务,其职责可以概括为三块:
- 标识:
moduleId(Vite 模块图 ID)与relativeModuleId(相对项目路径); - 状态与数据:
state()(含独有的queued)、meta()(自定义元数据)、diagnostic()(耗时/内存/worker 诊断)、logs()(收集期顶层日志); - 调度:
toTestSpecification()把模块(或部分用例)包装成可执行的测试规格。
它继承自TestSuite,因此 TestSuite API 中的children、errors()、options、project、module等能力同样适用。如需继续深入研究:
- 类实现与
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),仅供参考