Vitest TestCollection 详解:遍历与筛选测试集合的完整 API 指南
2026/9/14 4:11:37 网站建设 项目流程

Vitest TestCollection 详解:遍历与筛选测试集合的完整 API 指南

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

TestCollection是 Vitest 高级 API(@vitest/runner报告端)中描述"一个 suite 或 module 下所有顶层 suite 与 test 的集合"的核心数据结构,它本身是迭代器,并提供了sizeatarrayallSuitesallTeststestssuites等方法用于遍历和筛选。本文以官方文档 docs/api/advanced/test-collection.md 为骨架,结合仓库源码 reported-tasks.ts 的实现细节,帮助你彻底掌握如何在自定义 Reporter、插件或工具链中高效地读取、过滤和统计测试集合。

一、什么是 TestCollection

TestCollection表示一个 suite 或一个 module(测试模块)中的顶层TestSuite 与 TestCase 的集合。换句话说,任何一个 TestSuite 或 TestModule 实例上都挂载了一个children属性,它就是该节点直接子节点的集合:

module.children // TestCollection,包含该模块顶层的 suite 与 test suite.children // TestCollection,包含该 suite 顶层的 suite 与 test

从源码可以看到,TestCollection在 SuiteImplementation 的构造函数中被创建,并被赋值给children字段:

// packages/vitest/src/node/reporters/reported-tasks.ts abstract class SuiteImplementation extends ReportedTaskImplementation { public readonly children: TestCollection protected constructor(task: RunnerTestSuite | RunnerTestFile, project: TestProject) { super(task, project) this.children = new TestCollection(task, project) } }

因此TestCollection始终是"树中某个节点的直接子节点"的视图,而非全树的扁平列表;要拿到整棵子树,需要借助下面介绍的递归方法allSuites()/allTests()

二、集合本身就是一个迭代器

TestCollection实现了Symbol.iterator,因此可以直接用for...of遍历顶层子节点。其实现位于 reported-tasks.ts,内部把运行时的原始 task 通过getReportedTask映射为公开的TestSuite/TestCase实例:

* [Symbol.iterator](): Generator<TestSuite | TestCase, undefined, void> { for (const task of this.#task.tasks) { yield getReportedTask(this.#project, task) as TestSuite | TestCase } }

用法示例(遍历某 module 的所有直接子节点,打印类型与名称):

for (const child of module.children) { console.log(child.type, child.name) }

性能提示:绝大多数方法返回的是Generator(迭代器)而不是数组,目的是当集合很大且你并不需要全部元素时避免不必要的内存开销。如果你更习惯使用数组,可以用展开运算符把迭代器转成数组,例如[...children.allSuites()]。而array()方法本身也正是用Array.from(this)实现的(见 reported-tasks.ts)。

三、成员 API 逐个解析

1.size

get size(): number

返回集合中顶层 test 与 suite 的数量。注意:它只统计顶层节点,不包含嵌套在 suite 里的子 suite 和子 test。这在 reported-tasks.ts 的实现中非常直观——直接返回底层tasks数组的长度:

get size(): number { return this.#task.tasks.length }

例如下面的测试结构:

describe('outer', () => { it('a', () => {}) describe('inner', () => { it('b', () => {}) }) }) it('c', () => {})

module.children.size为 2(outersuite 与ctest),outer集合里的ainner不计入。

2.at(index)

function at(index: number): TestCase | TestSuite | undefined

返回指定索引处的 test 或 suite。支持负索引at(-1)表示最后一个元素,其实现先把负数归一化为this.size + index(见 reported-tasks.ts):

at(index: number): TestCase | TestSuite | undefined { if (index < 0) { index = this.size + index } return getReportedTask(this.#project, this.#task.tasks[index]) as TestCase | TestSuite | undefined }

索引越界时返回undefined

3.array()

function array(): (TestCase | TestSuite)[]

返回与集合内容一致的数组,适合直接使用mapfilterfindArray方法(这些方法不在迭代器上提供)。实现即Array.from(this),与[...collection]等价:

array(): (TestCase | TestSuite)[] { return Array.from(this) }

典型用法:

const names = module.children.array().map(child => child.name) const failedSuites = module.children.array().filter(child => child.type === 'suite' && child.errors().length)

4.allSuites()

function allSuites(): Generator<TestSuite, undefined, void>

返回本集合及其所有后代中的全部 suite(深度优先遍历)。实现通过递归yield* child.children.allSuites()完成(见 reported-tasks.ts):

* allSuites(): Generator<TestSuite, undefined, void> { for (const child of this) { if (child.type === 'suite') { yield child yield* child.children.allSuites() } } }

官方示例:检查是否存在收集(collection)阶段失败的 suite,比如语法错误:

for (const suite of module.children.allSuites()) { if (suite.errors().length) { console.log('failed to collect', suite.errors()) } }

5.allTests(state?)

function allTests(state?: TestState): Generator<TestCase, undefined, void>

返回本集合及其所有后代中的全部 test(深度优先遍历),并可选地按测试状态过滤。当传入state时,只有child.result().state与该状态一致的 test 才会被产出(见 reported-tasks.ts):

* allTests(state?: TestState): Generator<TestCase, undefined, void> { for (const child of this) { if (child.type === 'suite') { yield* child.children.allTests(state) } else if (state) { const testState = child.result().state if (state === testState) { yield child } } else { yield child } } }

官方示例:找出所有尚未执行完成的 test(例如在自定义报告器中判断是否有用例挂起):

for (const test of module.children.allTests()) { if (test.result().state === 'pending') { console.log('test', test.fullName, 'did not finish') } }

TestState类型定义为TestResult['state'](见 reported-tasks.ts),即'passed' | 'failed' | 'skipped' | 'pending'四种状态之一。result().state的语义与 TestCase.result() 完全一致:

  • pending:已收集但尚未运行完成;
  • passed:通过;
  • failed:失败;
  • skipped:收集阶段被跳过,或运行中被ctx.skip()动态跳过。

按状态过滤的用法:

// 只看失败用例 for (const test of module.children.allTests('failed')) { console.log('failed:', test.fullName, test.result().errors) } // 统计跳过用例数量 let skipped = 0 for (const _ of module.children.allTests('skipped')) { skipped++ }

6.tests(state?)

function tests(state?: TestState): Generator<TestCase, undefined, void>

allTests不同,tests只包含本集合的直接子 test(不进入嵌套 suite)。实现中遇到 suite 节点直接continue跳过(见 reported-tasks.ts):

* tests(state?: TestState): Generator<TestCase, undefined, void> { for (const child of this) { if (child.type !== 'test') { continue } if (state) { const testState = child.result().state if (state === testState) { yield child } } else { yield child } } }

state参数同样可选,语义与allTests相同。

7.suites()

function suites(): Generator<TestSuite, undefined, void>

tests对称,只产出本集合的直接子 suite,不递归到嵌套层级(见 reported-tasks.ts):

* suites(): Generator<TestSuite, undefined, void> { for (const child of this) { if (child.type === 'suite') { yield child } } }

四、API 速查对比表

方法范围是否递归返回类型支持状态过滤
size顶层节点计数number
at(index)顶层节点TestCase \| TestSuite \| undefined(支持负索引)
array()顶层节点(TestCase \| TestSuite)[]
suites()顶层 suiteGenerator<TestSuite>
tests(state?)顶层 testGenerator<TestCase>'passed' \| 'failed' \| 'skipped' \| 'pending'
allSuites()全部后代 suiteGenerator<TestSuite>
allTests(state?)全部后代 testGenerator<TestCase>✅ 同上

五、源码级应用实例:TestCollection 在 Vitest 内部如何被使用

理解TestCollection不能只看 API,它在 Vitest 自身的运行链路中扮演着关键角色。

1. 报告器事件分发(test-run.ts)

在 test-run.ts 的reportChildren中,Vitest 正是通过遍历TestCollection来递归地分发测试生命周期事件:

private async reportChildren(children: TestCollection) { for (const child of children) { if (child.type === 'test') { await this.vitest.report('onTestCaseReady', child) await this.vitest.report('onTestCaseResult', child) } else { await this.vitest.report('onTestSuiteReady', child) await this.reportChildren(child.children) // 递归进入子集合 await this.vitest.report('onTestSuiteResult', child) } } }

从这里可以看到child.children与迭代器配合的典型递归模式:先处理当前 suite,再通过child.children深入下一层,最后返回并报告结果。这也是自定义 Reporter 遍历测试树的推荐写法。

2. 报告器中的聚合统计(base.ts)

内置基础报告器在计算统计信息时同样依赖allTests()。例如 base.ts 中的用法:

const tests = Array.from(testSuite.children.allTests())

把递归迭代器一次性转为数组,再交给统计逻辑处理,正是文档推荐的[...iterator]模式在生产代码中的真实体现。

3. 公开导出

TestCollection类型经由 public/node.ts 导出,因此在自定义 Reporter、插件等面向 Node 环境的扩展代码中可以直接引用:

import type { TestCollection } from 'vitest/node'

六、实战:在自定义 Reporter 中组合使用

把上面所有 API 组合起来,可以写出一个"按模块输出失败用例与跳过用例清单"的迷你报告器逻辑:

import type { TestModule } from 'vitest/node' function summarizeModule(module: TestModule) { console.log(`module: ${module.moduleId}`) // 1. 顶层统计 console.log(`top-level items: ${module.children.size}`) // 2. 递归找出所有失败测试 const failed = [...module.children.allTests('failed')] console.log(`failed tests: ${failed.length}`) for (const test of failed) { console.log(` ✗ ${test.fullName}`) } // 3. 递归找出所有跳过测试 const skipped = [...module.children.allTests('skipped')] console.log(`skipped tests: ${skipped.length}`) // 4. 只统计顶层直接测试(不含嵌套 suite 内) const topLevelTests = [...module.children.tests()] console.log(`top-level tests: ${topLevelTests.length}`) // 5. 找出收集失败的嵌套 suite for (const suite of module.children.allSuites()) { if (suite.errors().length) { console.log('failed to collect:', suite.errors()) } } // 6. 负索引取最后一个顶层节点 const last = module.children.at(-1) console.log('last child:', last?.type, last?.name) }

七、常见问题与注意事项

  1. size与真实用例总数不一致size只算顶层节点。若想获得包含嵌套用例的完整数量,需要手动累加allTests()allSuites()的结果。
  2. 迭代器是"一次性"的Generator被消费后无法重复遍历。如果同一份集合需要多次遍历(例如先统计失败再统计跳过),优先array()或先展开成数组,避免重复创建迭代器的开销。
  3. state过滤基于result().state:对于尚未开始运行的用例,result().statepending;被todo/skip标记的用例最终表现为skipped。过滤参数与 TestCase.result() 的状态枚举保持一致。
  4. 递归方向allSuites/allTests是深度优先(先处理完当前 suite 的子树再进入下一个兄弟节点),与reportChildren的事件顺序一致,便于复现报告顺序。
  5. 顶层 vs 嵌套的取舍:当你的报告逻辑只关心模块直属结构(如 UI 树的第一层折叠视图)时,使用tests()/suites()更精准;需要全量统计时再使用allTests()/allSuites()

八、延伸阅读

  • TestSuite:suite 节点及其childrenerrors()等成员
  • TestCase:test 节点的result()fullNameannotations()等成员
  • TestModule:模块级节点,其children即模块的顶层TestCollection
  • TestProject:与集合关联的项目实例,可用于创建 Specification 等后续操作
  • Reporters 指南 与 高级 Reporter API:在真实报告器中使用TestCollection的完整场景

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

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

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

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

立即咨询