
Vitest TestCollection 详解遍历与筛选测试集合的完整 API 指南【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitestTestCollection是 Vitest 高级 APIvitest/runner报告端中描述一个 suite 或 module 下所有顶层 suite 与 test 的集合的核心数据结构它本身是迭代器并提供了size、at、array、allSuites、allTests、tests、suites等方法用于遍历和筛选。本文以官方文档 docs/api/advanced/test-collection.md 为骨架结合仓库源码 reported-tasks.ts 的实现细节帮助你彻底掌握如何在自定义 Reporter、插件或工具链中高效地读取、过滤和统计测试集合。一、什么是 TestCollectionTestCollection表示一个 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](): GeneratorTestSuite | 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.sizeget 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为 2outersuite 与ctestouter集合里的a与inner不计入。2.at(index)function at(index: number): TestCase | TestSuite | undefined返回指定索引处的 test 或 suite。支持负索引at(-1)表示最后一个元素其实现先把负数归一化为this.size index见 reported-tasks.tsat(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)[]返回与集合内容一致的数组适合直接使用map、filter、find等Array方法这些方法不在迭代器上提供。实现即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(): GeneratorTestSuite, undefined, void返回本集合及其所有后代中的全部 suite深度优先遍历。实现通过递归yield* child.children.allSuites()完成见 reported-tasks.ts* allSuites(): GeneratorTestSuite, 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): GeneratorTestCase, undefined, void返回本集合及其所有后代中的全部 test深度优先遍历并可选地按测试状态过滤。当传入state时只有child.result().state与该状态一致的 test 才会被产出见 reported-tasks.ts* allTests(state?: TestState): GeneratorTestCase, 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): GeneratorTestCase, undefined, void与allTests不同tests只包含本集合的直接子 test不进入嵌套 suite。实现中遇到 suite 节点直接continue跳过见 reported-tasks.ts* tests(state?: TestState): GeneratorTestCase, 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(): GeneratorTestSuite, undefined, void与tests对称只产出本集合的直接子 suite不递归到嵌套层级见 reported-tasks.ts* suites(): GeneratorTestSuite, 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()顶层 suite否GeneratorTestSuite—tests(state?)顶层 test否GeneratorTestCase✅passed \| failed \| skipped \| pendingallSuites()全部后代 suite是GeneratorTestSuite—allTests(state?)全部后代 test是GeneratorTestCase✅ 同上五、源码级应用实例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) }七、常见问题与注意事项size与真实用例总数不一致size只算顶层节点。若想获得包含嵌套用例的完整数量需要手动累加allTests()与allSuites()的结果。迭代器是一次性的Generator被消费后无法重复遍历。如果同一份集合需要多次遍历例如先统计失败再统计跳过优先array()或先展开成数组避免重复创建迭代器的开销。state过滤基于result().state对于尚未开始运行的用例result().state为pending被todo/skip标记的用例最终表现为skipped。过滤参数与 TestCase.result() 的状态枚举保持一致。递归方向allSuites/allTests是深度优先先处理完当前 suite 的子树再进入下一个兄弟节点与reportChildren的事件顺序一致便于复现报告顺序。顶层 vs 嵌套的取舍当你的报告逻辑只关心模块直属结构如 UI 树的第一层折叠视图时使用tests()/suites()更精准需要全量统计时再使用allTests()/allSuites()。八、延伸阅读TestSuitesuite 节点及其children、errors()等成员TestCasetest 节点的result()、fullName、annotations()等成员TestModule模块级节点其children即模块的顶层TestCollectionTestProject与集合关联的项目实例可用于创建 Specification 等后续操作Reporters 指南 与 高级 Reporter API在真实报告器中使用TestCollection的完整场景【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考