close

性能分析

Rstest 提供了多种工具和方法来分析测试运行的性能,帮助你识别和解决性能瓶颈。

常见性能瓶颈

当测试运行出现明显性能下降时,通常应先区分瓶颈位于“构建阶段”还是“测试执行阶段”:

  • 如果测试启动前等待时间较长,且在少量代码变更后重新运行仍然耗时较高,通常说明瓶颈位于构建阶段。
  • 如果测试能够快速开始执行,但单个用例或测试文件本身耗时较长,通常说明瓶颈位于测试执行阶段。

围绕这两类瓶颈,可以分别从构建阶段和测试执行阶段进行排查。

构建性能排查

对于使用 jsdomhappy-dom 等类浏览器测试环境的项目,构建阶段通常是最常见的性能瓶颈之一。原因在于这类环境下,rstest 默认会将 node_modules 中的第三方依赖一并打包,而不是像 node 环境那样默认 externalize。

这意味着以下情况都可能导致测试运行变慢:

  • 某个测试入口间接引入了体积较大的 UI 库、图表库或编辑器库。
  • 测试仅依赖少量 DOM 能力,但构建产物中包含了大量第三方运行时代码。

对于这类场景,推荐按照以下顺序进行排查。

1. 启用 rstest 调试输出

运行测试时添加 DEBUG=rstest

DEBUG=rstest rstest run

启用 DEBUG 后,rstest 会输出更详细的构建日志,并将临时构建产物写入磁盘。默认输出目录为 dist/.rstest-temp;如果你配置了 output.distPath.root,则会写入对应目录。

此时应优先观察以下两个信号:

  • 构建相关日志阶段是否存在明显的长时间停留。
  • 临时输出目录中是否存在异常大的产物文件。

如果已经怀疑问题与 bundle 体积有关,这一步通常可以提供第一轮证据。

2. 识别第三方依赖带来的体积开销

jsdomhappy-dom 等类浏览器环境中,构建产物较大并不罕见,因为 rstest 默认会打包第三方依赖。

此时可以重点检查以下内容:

  • 较大的产物文件是否对应某个测试入口及其依赖链。
  • 是否引入了体积较大的 node_modules 包。
  • 是否仅使用了少量 API,却将整个包打入了产物。

如果问题集中在第三方依赖本身,通常无需立即引入更复杂的 profiling 工具,优先调整打包策略会更直接。

3. 使用 output.bundleDependencies 验证打包策略的影响

可以将 output.bundleDependencies 设为 false,使类浏览器环境也采用与 node 环境一致的第三方依赖 externalize 策略:

rstest.config.ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  testEnvironment: 'jsdom',
  output: {
    bundleDependencies: false,
  },
});

修改后,建议重新使用 DEBUG=rstest 运行一次,并对比以下两项指标:

  • 构建耗时是否明显下降。
  • dist/.rstest-temp 中的临时产物是否明显减小。

如果两者都得到改善,通常可以判断性能瓶颈主要来自第三方依赖的打包成本。

4. 使用 output.externals 进行细粒度控制

如果只需要 externalize 少数几个体积较大的依赖,而不是关闭所有第三方依赖的打包,可以继续使用 output.externals

rstest.config.ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  output: {
    externals: ['react', 'lodash'],
  },
});

这种方式适用于以下场景:

  • 只有少数几个包的体积或构建成本显著偏高。
  • 仍然希望其余依赖保留 bundle 带来的优化。
  • 已经确认瓶颈集中在少量第三方依赖上。

5. 在构建瓶颈仍不明确时使用 Rsdoctor

当已经确认问题位于构建阶段,但仍无法确定瓶颈究竟来自入口、loader、插件还是某条依赖链时,再使用下文的 Rsdoctor 会更高效。前面的步骤更适合快速判断问题是否与 bundle 体积相关,而 Rsdoctor 更适合回答编译时间的具体分布。

Warning

上面的 output.bundleDependencies: false 只适用于非浏览器模式。在 浏览器模式 下,依赖始终会被打包,因此不能用这条路径来缩小 bundle。

测试执行性能排查

当问题已经定位到测试执行阶段时,排查重点通常不再是构建产物体积,而是具体哪个测试文件、哪个测试用例,或哪一段运行时代码消耗了更多时间。

1. 使用 verbose 报告器观察测试文件和测试用例耗时

启用 verbose reporter 后,可以直接看到测试文件和单个测试用例的执行时间。这通常是定位执行阶段瓶颈的第一步,因为它可以先帮助你缩小范围,判断问题集中在某个测试文件、某组测试,还是少量特定用例上。

2. 在范围缩小后查看更细的耗时分布

当已经知道慢点集中在哪些测试文件或测试用例上,但仍不清楚具体耗时来自哪一段运行时代码时,可以进一步使用下文的 SamplyNode.js profiling

  • Samply 适合查看 rstest 主进程与测试进程的详细耗时分布。
  • Node.js profiling 适合继续分析 Node.js 侧的 CPU 或内存开销。

使用 Rsdoctor

Rsdoctor 是一款为 Rspack 生态量身打造的构建分析工具。

当你需要调试 Rstest 的构建产物或构建过程时,可以借助 Rsdoctor 来提升排查问题的效率。

快速上手

在 Rstest 中,你可以通过以下步骤开启 Rsdoctor 分析:

  1. 安装 Rsdoctor 插件:
npm
yarn
pnpm
bun
deno
npm add @rsdoctor/rspack-plugin -D
  1. 在 CLI 命令前添加 RSDOCTOR=true 环境变量:
package.json
{
  "scripts": {
    "test:rsdoctor": "RSDOCTOR=true rstest run"
  }
}

由于 Windows 不支持上述用法,你也可以使用 cross-env 来设置环境变量,这可以确保在不同的操作系统中都能正常使用:

package.json
{
  "scripts": {
    "test:rsdoctor": "cross-env RSDOCTOR=true rstest run"
  },
  "devDependencies": {
    "cross-env": "^7.0.0"
  }
}

在项目内执行上述命令后,Rstest 会自动注册 Rsdoctor 的插件,并在构建完成后打开本次构建的分析页面,请参考 Rsdoctor 文档 来了解完整功能。

rsdoctor-rstest-outputs

使用 Samply

注意:为了能在 macOS 中对 Node.js 侧代码进行 profiling 需要 22.16+ 版本。

Samply 支持同时对 Rstest 主进程和测试进程进行性能分析,可通过如下步骤进行完整的性能分析:

运行以下命令启动性能分析:

samply record -- node --perf-prof --perf-basic-prof --interpreted-frames-native-stack {your_node_modules_folder}/@rstest/core/bin/rstest.js

命令执行完毕后会自动打开分析结果。

Rstest 的 JavaScript 代码通常执行在 Node.js 线程里,选择 Node.js 线程查看 Node.js 侧的耗时分布。

rstest-samply-profiling

Node.js profiling

你也可以使用 Node.js 内置的性能分析工具来分析 Rstest 的性能。

例如,你可以使用 --heap-prof 标志来启用堆内存分析:

node --heap-prof --heap-prof-dir=./heap-prof ./node_modules/@rstest/core/bin/rstest.js

堆内存分析文件将生成在 ./heap-prof 目录中。你可以使用 Visual Studio CodeChrome DevTools 等工具来分析这些文件。

你也可以使用 --inspect 标志来启用 Node.js 调试器

node --inspect ./node_modules/@rstest/core/bin/rstest.js watch