ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vitest benchmark 配置完全指南:从 include/exclude 到自定义 Provider 与结果持久化

Vitest benchmark 配置完全指南:从 include/exclude 到自定义 Provider 与结果持久化 Vitest benchmark 配置完全指南从 include/exclude 到自定义 Provider 与结果持久化【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest导读benchmark是 Vitest 中专门用于性能基准测试Benchmarking的顶层配置块它在test配置下工作用于控制vitest bench命令的行为。通过本文你将掌握 benchmark 项目的启用机制、文件匹配规则、原始样本保留、可插拔的 Provider 架构以及如何抑制模块运行器开销警告——每个配置项都会结合 vitest/src/node/types/benchmark.ts 与 vitest/src/node/defaults.ts 中的源码默认值逐一剖析并给出可直接复制运行的配置示例。配置总览类型与默认值benchmark选项的类型为{ include?, exclude?, ... }即一个对象字段全部可选。完整的用户配置接口定义在 types/benchmark.ts所有字段的默认值集中声明在 src/defaults.ts 的benchmarkConfigDefaults中配置项类型默认值作用enabledbooleanfalse是否启用独立的 benchmark 项目includestring[][**/*.{bench,benchmark}.?(c|m)[jt]s?(x)]匹配基准测试文件的 globexcludestring[][node_modules, dist, .idea, .git, .cache]排除目录继承自测试的默认排除项includeSourcestring[][]源内基准测试文件的 globretainSamplesbooleanfalse是否在结果中保留每次迭代的samples数组providerstringundefined使用内置 provider自定义基准执行引擎模块路径suppressExportGetterWarningsbooleanfalse是否抑制导出 getter 访问过多警告projectNamestring内部字段运行时自动填充${projectName}占位符注意exclude的默认值在文档中表现为[node_modules, dist, .idea, .git, .cache]它复用了defaultExclude见 defaults.ts 的**/node_modules/**、**/.git/**同时内置了dist、.idea、.cache等构建产物与 IDE 目录的排除。benchmark.enabled独立基准测试项目开关类型boolean默认值false当设为true时Vitest 会在你的常规测试项目之外克隆出一个专用 benchmark 项目它复用父项目的 Vite 配置但使用 benchmark 专属的 include/exclude 规则、强制串行执行并将benchfixture 暴露给匹配benchmark.include的文件。运行vitest bench命令会自动启用该选项无需手动配置。从源码看这个克隆项目的机制实现在 projects/resolveProjects.ts 的expandBenchmarksInEntries函数中对每个启用了 benchmark 的项目或benchmarkOnly模式下的所有项目生成一个名为原项目名 (bench)无名字时叫bench的新项目条目并注入以下关键约束maxWorkers: 1、maxConcurrency: 1——基准测试文件之间永无并发testTimeout提升至至少 60 秒、hookTimeout提升至至少 120 秒避免长基准被误杀sequence.concurrent: false且分配独立的groupOrder——基准始终在单独的隔离分组中串行执行coverage.enabled: false、typecheck.enabled: false——基准项目不参与覆盖率与类型检查原始项目中的benchmark.enabled被重置为false确保常规vitest运行时不会把基准文件当普通测试跑。同时 core.ts 表明当 CLI 传入--benchmark即benchmarkOnly时运行前会把projects过滤为只保留config.benchmark.enabled true的 benchmark 变体项目。import { defineConfig } from vitest/config export default defineConfig({ test: { benchmark: { enabled: true, // 让 vitest 同时运行普通测试与基准测试 }, }, })启用后运行vitest普通测试先执行基准测试随后在独立隔离分组中执行——两者互不干扰基准结果不会被测试执行噪声污染。benchmark.include / benchmark.exclude文件匹配规则include类型string[]默认[**/*.{bench,benchmark}.?(c|m)[jt]s?(x)]exclude类型string[]默认[node_modules, dist, .idea, .git, .cache]include决定了哪些文件属于 benchmark 项目。默认 glob 的展开含义是匹配所有文件名形如*.bench.ts、*.benchmark.js、*.bench.mts、*.benchmark.cts、*.bench.tsx等变体的文件——即文件名以.bench或.benchmark结尾扩展名支持.js/.ts/.jsx/.tsx/.mjs/.mts/.cjs/.cts的任意组合。判定基准文件的标准是文件名而不是内容中是否使用了benchfixture。把parser.test.ts改名为parser.bench.ts或调整benchmark.include文件才会进入 benchmark 项目反之在普通测试文件中使用{ bench }会直接抛出错误。这一点由validateBenchmarkProject见 runtime/benchmark.ts 中bench工厂函数的第一步保证。exclude则给出被忽略的目录。自定义时通常写成export default defineConfig({ test: { benchmark: { include: [bench/**/*.bench.ts], exclude: [bench/legacy/**, node_modules], }, }, })benchmark.includeSource源内基准测试类型string[]默认值[]与常规配置的includeSource语义一致当定义该 glob 后所有匹配文件凡是包含import.meta.vitest代码块的都会被执行基准测试。它让基准测试可以直接写在源码文件内部例如import { bench } from vitest if (import.meta.vitest) { bench(parse in-source, () JSON.parse({a:1})).run() }export default defineConfig({ test: { benchmark: { includeSource: [src/**/*.ts], }, }, })这样源码文件即文档、即测试、即基准适合把性能基线跟实现代码放在一起维护。benchmark.retainSamples保留逐次迭代原始样本类型boolean默认值false开启后每个基准结果的samples数组每次迭代的计时会被保留下来。默认关闭以节省内存——一次基准可能运行成千上万次迭代每个样本都是一份浮点数组。当自定义 reporter 或 API 消费者需要原始样本做更精细的统计如绘制分布图、计算分位数时再开启export default defineConfig({ test: { benchmark: { retainSamples: true, }, }, })源码层面该值会被直接透传给 Tinybench 实例的retainSamples选项见 runtime/benchmark/default-provider.tsconst tinybench new Tinybench({ signal: test.context.signal, name: ${test.fullTestName} ${currentIndex}, retainSamples: config.benchmark.retainSamples, ...options, now, })benchmark.provider替换基准执行引擎类型string默认值undefined使用内置 providerbenchmark.provider指定一个模块路径该模块的 default export 必须实现BenchmarkProvider接口。相对路径从项目根目录解析。内置 provider 基于 Tinybench详见 Benchmarking 指南替换 provider 即可使用其他基准引擎或执行策略。完整的 Provider API 与生命周期说明见 Custom Benchmark Provider 指南这里摘出核心要点接口BenchmarkProvider只有一个方法run(group: BenchmarkGroup): PromiseBenchResult[]见 runtime/benchmark.ts。group包含test其context.signal在取消时 abort、config当前项目解析后的 benchmark 配置、registrations可运行的基准列表含name/fn/fnOpts与options.run()或bench.compare()传入的运行选项。职责provider 必须尊重运行选项与注册选项按引擎生命周期执行每个基准函数及其beforeAll/beforeEach/afterEach/afterAllhooks执行失败应抛出错误使测试失败。返回值run必须为每个可运行注册项返回一个BenchResult按name与注册项一一对应它同时是.run()返回值、对比表、reporter 与持久化结果的唯一数据来源。自定义引擎必须把测量结果转换为vitest导出的 Tinybench 兼容BenchResult结构。生命周期provider 模块在首次使用时导入其 default export 在worker 存活期间被缓存见 runtime/benchmark.ts 的resolveBenchmarkProviderAPI 没有独立的 setup/teardown hooks需要 worker 级状态时直接挂在 provider 对象上。特殊规则bench.from()创建的注册项由 Vitest 自行加载结果不会传给 providerloadProviderModuleruntime/benchmark.ts会在模块无 default export 时抛出明确错误。一个最小可用的自定义 provider封装 Tinybench如下import { defineConfig } from vitest/config export default defineConfig({ test: { benchmark: { provider: ./benchmark-provider.ts, }, }, })import type { BenchmarkProvider } from vitest import { Bench } from tinybench const provider { async run({ test, config, registrations, options }) { const bench new Bench({ signal: test.context.signal, retainSamples: config.retainSamples, ...options, }) for (const { name, fn, fnOpts } of registrations) { bench.add(name, fn, fnOpts) } await bench.run() return bench.tasks.map((task) { const result task.result if (result.state errored) { throw result.error } if (result.state ! completed) { throw new Error(Benchmark ${task.name} ended in the ${result.state} state) } return { ...result, name: task.name, } }) }, } satisfies BenchmarkProvider export default provider若在 provider 中使用 Tinybench需把它加入项目的直接依赖。benchmark.suppressExportGetterWarnings抑制导出 getter 警告类型boolean默认值false默认情况下 Vitest 通过 Vite 的模块运行器由experimental.viteModuleRunner配置在 Node.js 中运行测试该运行器会把每个模块导出都包装成 getter——每次访问导入的绑定都要经过__vite_ssr_module__.value之类的间接调用。普通测试中这点开销微不足道但基准函数被调用数百万次时getter 调用本身就可能主导测量结果详见 Module Runner Overhead。Vitest 在基准运行期间跟踪 getter 访问次数workerState.getterTracker一旦检测到过多访问就会打印警告。suppressExportGetterWarnings: true用于在以下两种场景静默该警告你有意接受了这部分开销例如只是粗略对比、不追求极致精度警告过于频繁、而该基准的 getter 开销确实可忽略。其底层逻辑见 runtime/benchmark.ts 的runGroup执行完provider.run后只有在该配置为false时才会检查getterTracker.getExcessiveInvocations()并打印黄色警告警告中会列出被过度访问的模块导出moduleId exportName。export default defineConfig({ test: { benchmark: { suppressExportGetterWarnings: true, }, }, })更好的做法不是压制警告而是消除 getter 开销在基准函数内先用局部变量缓存导入引用const _parse parse或直接基准已构建的产物通过包名导入而非../src/index.ts甚至为基准项目关闭experimental.viteModuleRunner让 Node 原生执行 ESM。与其他功能的联动配置项如何支撑基准实战benchmark配置不是孤立的开关它与vitest bench命令、benchfixture、结果持久化机制共同构成完整的基准工作流vitest bench自动启用 benchmark 项目CLI 侧 cli/cac.ts 注册了bench [...filters]子命令其 action 在 cli/cac.ts 中设置options.benchmarkOnly true随后 core.ts 据此只保留 benchmark 变体项目。支持文件名过滤器与-t/--testNamePatternvitest bench parser、vitest bench -t JSON。bench.from()读取历史结果bench.from(name, source)不执行函数而是从路径或返回数据的函数含 Promise读取已存储结果参与bench.compare()。读取失败会抛出 could not find a result file 错误见 runtime/benchmark.ts。writeResult持久化单次基准成功后把结果写入 JSON路径相对项目根目录支持${projectName}占位符做跨项目产物失败则不写。文件写入实现是 node/benchmark.ts 的BenchmarkManager.writeResult。路径安全校验BenchmarkManager.resolvenode/benchmark.ts会拒绝解析到项目根目录之外的所有路径——writeResult/bench.from()接受任意输入但绝不允许基准文件读写工作区之外的文件。跨项目对比bench.compare()内传入{ perProject: true }的基准会被额外收集到运行结束时的跨项目汇总表中每个项目读自己的${projectName}产物。小结benchmark配置块用 8 个字段精确刻画了 Vitest 的基准测试运行形态enabled决定是否克隆出独立的基准项目include/exclude/includeSource划定文件边界retainSamples控制原始样本的保留provider允许把执行引擎整个替换掉suppressExportGetterWarnings管理模块运行器开销的告警。理解这些配置与 resolveProjects.ts 中的项目克隆、runtime/benchmark.ts 中的注册/执行编排、node/benchmark.ts 中的产物读写三者之间的关系你就能把 Vitest 的基准能力真正用起来——无论是内置 Tinybench 的零配置体验还是面向特殊引擎的自定义 Provider 方案。想进一步深入可以继续阅读Benchmarking 指南——完整的基准编写、对比、断言与稳定性实践Custom Benchmark Provider——Provider 接口与生命周期详解benchmark 类型定义——所有字段的源码级注释内置 provider 实现——Tinybench 如何被封装成BenchmarkProvider。【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表