> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 性能分析

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

## 使用 Agent Skills

如果你在使用支持 Skills 的 Coding Agent，可以安装 [rstest-debugging](https://github.com/rstackjs/agent-skills#rstest-debugging) 技能来系统化 Debug Rstest 问题，包括性能回归。当 Rstest 比预期更慢、比 Jest 或 Vitest 更慢，或需要在尝试配置或代码改动前判断瓶颈是在构建启动还是测试执行中时，可以使用该 Skill 辅助排查。

```bash
npx skills add rstackjs/agent-skills --skill rstest-debugging
```

安装后，让 Coding Agent 协助完成性能问题排查即可。

## 常见性能瓶颈

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

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

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

如果希望直接测量这一划分，而不是靠现象推断，可以使用 [`--trace`](#using-trace) 运行一次测试。

### 构建性能排查

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

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

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

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

**1. 启用 rstest 调试输出**

运行测试时添加 `DEBUG=rstest`：

```bash
DEBUG=rstest rstest run
```

启用 DEBUG 后，rstest 会输出更详细的构建日志，并将临时构建产物写入磁盘。默认输出目录为 `dist/.rstest-temp`；如果你配置了 [`output.distPath.root`](/zh/config/build/output.md#outputdistpath)，则会写入对应目录。

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

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

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

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

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

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

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

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

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

可以将 [`output.bundleDependencies`](/zh/config/build/output.md#outputbundledependencies) 设为 `false`，使类浏览器环境也采用与 `node` 环境一致的第三方依赖 externalize 策略：

```ts title="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`](/zh/config/build/output.md#outputexternals)：

```ts title="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` 只适用于非浏览器模式。在 [浏览器模式](/zh/guide/browser-testing.md) 下，依赖始终会被打包，因此不能用这条路径来缩小 bundle。
:::

### 测试执行性能排查

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

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

启用 [`verbose reporter`](/zh/guide/basic/reporters.md#详细报告器) 后，可以直接看到测试文件和单个测试用例的执行时间。这通常是定位执行阶段瓶颈的第一步，因为它可以先帮助你缩小范围，判断问题集中在某个测试文件、某组测试，还是少量特定用例上。

**2. 按 phase、suite、case 拆解本次运行**

对同一批测试使用 [`--trace`](#using-trace) 重新运行一次。它的摘要会对最慢的 phase、文件和测试用例排序，通常足以判断耗时落在构建相关阶段、setup 文件，还是测试体本身。

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

当已经知道慢点集中在哪些测试文件或测试用例上，但仍不清楚具体耗时来自哪一段运行时代码时，需要切换到 profiler。在 [Samply](#使用-samply) 和 [Node.js profiling](#nodejs-profiling) 之间如何选，参考下文 [Profiler](#profiler)。

## 使用 --trace \{#using-trace}

`--trace` 会把一次运行按 phase、suite、case 拆分成 slices。当 reporter 末尾的 `Duration` 单值不足以判断时间分布时，可以使用它定位瓶颈。

```bash
npx rstest run --trace
```

`--trace` 是 CLI-only 的可选开关。开启后每次运行会记录带 status 和 retry count 标注的 suite 与 case slices，在每个 phase 边界采样一次 heap 计数，并为每个测试文件的各个 phase 生成一个 slice：

- `prepare`：初始化 worker 运行时、全局 API，以及启用覆盖率时的 coverage provider。每个测试文件会进入两次，分别位于 `envSetup` 前后。
- `envSetup`：初始化 test environment。`node` 环境下，以及非 isolate 的 worker 已锁定该环境时，耗时接近 0。
- `load`：接收该测试入口的构建产物。host 侧生成产物的耗时会记录为 host span。
- `setupFiles`：执行配置的 setup 文件。
- `collect`：执行测试文件的顶层代码。`describe` 的函数体会被延迟到 `tests` 中执行。
- `tests`：构建 suite 与 case 树，然后运行用例及其 hooks。
- `coverage`：从 provider 收集覆盖率数据。未启用覆盖率，或该文件在此之前 bail 或抛错时，不会出现。
- `teardown`：清理 coverage provider 和 worker 状态；启用 `isolate` 时还会清理 test environment。

浏览器模式只记录 `prepare` 和 `tests` 两个 phase，由 host 根据页面发来的消息计时。

Rstest 会向 `<rootPath>/.rstest/` 写入两个文件，并在 watch 模式每次重新运行时替换它们：

- `trace-<timestamp>.summary.md`：对最慢的 phase、文件和测试用例排序的 markdown 摘要，相同内容会同时打印到终端。
- `trace-<timestamp>.json`：Perfetto-compatible 的 trace，用于可视化 UI。

建议先看摘要。当你还需要观察多个 worker 之间的工作如何重叠时，再打开 trace。

![Perfetto UI 中显示在 rstest 仓库执行 npx rstest --trace 产出的 trace](https://assets.rspack.rs/rstest/assets/rstest-trace-perfetto.png)

在 TTY 下，Rstest 会启动一个本地 helper server，使打印出的链接直接在 Perfetto UI 中打开 trace。在 CI 下不会启动该 server，此时可以将 trace 文件作为 artifact 上传，再拖入 [ui.perfetto.dev](https://ui.perfetto.dev) 查看。

:::tip
借助 Coding Agent 排查性能问题时，把两个文件都交给它：摘要给出排序结果，trace 包含完整事件数据。可配合 [rstest-debugging](#使用-agent-skills) 技能使用。

:::

## 使用 Rsdoctor

[Rsdoctor](https://rsdoctor.rs/) 是一款为 Rspack 生态量身打造的构建分析工具。

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

### 快速上手

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

1. 安装 Rsdoctor 插件：


```sh [npm]
npm add @rsdoctor/rspack-plugin -D
```

```sh [yarn]
yarn add @rsdoctor/rspack-plugin -D
```

```sh [pnpm]
pnpm add @rsdoctor/rspack-plugin -D
```

```sh [bun]
bun add @rsdoctor/rspack-plugin -D
```

```sh [deno]
deno add npm:@rsdoctor/rspack-plugin -D
```

2. 在 CLI 命令前添加 `RSDOCTOR=true` 环境变量：

```json title="package.json"
{
  "scripts": {
    "test:rsdoctor": "RSDOCTOR=true rstest run"
  }
}
```

由于 Windows 不支持上述用法，你也可以使用 [cross-env](https://npmjs.com/package/cross-env) 来设置环境变量，这可以确保在不同的操作系统中都能正常使用：

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

在项目内执行上述命令后，Rstest 会自动注册 Rsdoctor 的插件，并在构建完成后打开本次构建的分析页面，请参考 [Rsdoctor 文档](https://rsdoctor.rs/) 来了解完整功能。

![rsdoctor-rstest-outputs](https://assets.rspack.rs/rstest/assets/rsdoctor-rstest-outputs.png)

## Profiler

根据需要分析的目标选择合适的 profiler。可以先确认 [`--trace`](#using-trace) 是否已经能回答你的问题，它不需要任何外部工具。

- **CPU 耗时分布**：推荐使用 [Samply](#使用-samply)，可同时分析 rstest 主进程和 worker fork 进程。
- **内存分配**：使用 `--heap-prof`，参见 [Node.js profiling](#nodejs-profiling)。Samply 不采集内存数据。
- **断点 / 单步调试**：使用 `--inspect`，参见 [Node.js profiling](#nodejs-profiling)。

## 使用 Samply

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

[Samply](https://github.com/mstange/samply) 支持同时对 Rstest 主进程和测试进程进行性能分析，可通过如下步骤进行完整的性能分析：

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

```bash
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](https://assets.rspack.rs/rstest/assets/rstest-samply-profiling.png)

## Node.js profiling

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

例如，你可以使用 `--heap-prof` 标志来启用堆内存分析：

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

堆内存分析文件将生成在 `./heap-prof` 目录中。你可以使用 [Visual Studio Code](https://code.visualstudio.com/docs/nodejs/profiling#_analyzing-a-profile) 或 [Chrome DevTools](https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots) 等工具来分析这些文件。

你也可以使用 `--inspect` 标志来启用 [Node.js 调试器](https://nodejs.org/en/learn/getting-started/debugging)。

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