运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

测试运行器

Bun 的快速内置 Jest 兼容测试运行器,支持 TypeScript、生命周期钩子、模拟和监视模式

Bun 自带一个快速且与 Jest 兼容的测试运行器。测试在 Bun 运行时中执行,并支持以下功能。

  • TypeScript 和 JSX
  • 生命周期钩子
  • 快照测试
  • UI 与 DOM 测试
  • 使用 --watch 的监视模式
  • 使用 --preload 的脚本预加载

运行测试

terminal
bun test

你可以使用类似 Jest 的 API,以 JavaScript 或 TypeScript 编写测试。请参阅编写测试。

math.test.ts
import { expect, test } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});

测试运行器会递归搜索工作目录中符合以下模式的文件:

  • *.test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
  • *_test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
  • *.spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
  • *_spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}

若要筛选要运行的_测试文件_,请向 bun test 传递其他位置参数。路径与任一筛选条件匹配的测试文件都会运行。筛选条件通常是文件名或目录名;目前尚不支持 glob 模式。

terminal
bun test <filter> <filter> ...

如果要根据 测试名称 过滤,使用 -t/--test-name-pattern 标志。

terminal
# 运行名称中含 "addition" 的所有测试或测试套件
bun test --test-name-pattern addition

若要运行特定文件,确保路径以 ./ 或 / 开头,以区别于筛选名。

terminal
bun test ./test/specific-file.test.ts

默认情况下,测试运行器会在单个进程中运行所有测试:它会加载所有 --preload 脚本(请参阅生命周期),然后在一个共享全局环境中运行每个文件。传入 --parallel 可将文件分散到各个 CPU 核心上运行。如果测试失败,测试运行器会以非零退出代码退出。

CI/CD 集成

bun test 支持多种 CI/CD 集成方案。

GitHub Actions

bun test 会自动检测其是否在 GitHub Actions 中运行,并直接向控制台输出 GitHub Actions 注释。

无需额外配置,只需在工作流程中安装 bun 并运行 bun test。

如何在 GitHub Actions 工作流程中安装 bun

要在 GitHub Actions 工作流程中使用 bun test,添加如下步骤:

.github/workflows/test.yml
jobs:
  build:
    name: build-app
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Install bun
        uses: oven-sh/setup-bun@v2
      - name: Install dependencies # (假设你的项目有依赖)
        run: bun install # 如果喜欢,也可以用 npm/yarn/pnpm 代替
      - name: Run tests
        run: bun test

JUnit XML 报告(GitLab 等)

要写入 JUnit XML 报告,请将 --reporter=junit 与 --reporter-outfile 一起传入。

terminal
bun test --reporter=junit --reporter-outfile=./bun.xml

bun test 仍会像往常一样写入 stdout/stderr,并在运行结束时将 JUnit XML 报告写入指定路径。

JUnit XML 是 CI/CD 管道中报告测试结果的流行格式。

超时设置

使用 --timeout 标志指定每个测试的超时时间,单位为毫秒。如果测试超时,Bun 会将其标记为失败。默认值为 5000。

terminal
# 默认值是 5000
bun test --timeout 20

并发测试执行

要跨 CPU 核运行测试文件,请参阅 --parallel。以下标志控制文件_内部_测试的并发性。

默认情况下,Bun 会在每个测试文件中按顺序运行所有测试。并发执行会并行运行异步测试,从而加快包含相互独立测试的测试套件。

--concurrent 标志

使用 --concurrent 标志,实现各测试文件内部的所有测试并发执行:

terminal
bun test --concurrent

启用此标志后,除非测试标记为 test.serial,否则所有测试都会并行运行。

--max-concurrency 标志

用 --max-concurrency 标志控制最大并发测试数量:

terminal
# 限制最大并发测试为 4 个
bun test --concurrent --max-concurrency 4

# 默认值是 20
bun test --concurrent

此限制有助于在运行大量并发测试时防止资源耗尽。默认值为 20。

test.concurrent

单独标记测试为并发执行,即使未使用 --concurrent 也能并行运行:

math.test.ts
import { test, expect } from "bun:test";

// 这些测试相互并行运行
test.concurrent("concurrent test 1", async () => {
  await fetch("/api/endpoint1");
  expect(true).toBe(true);
});

test.concurrent("concurrent test 2", async () => {
  await fetch("/api/endpoint2");
  expect(true).toBe(true);
});

// 此测试顺序执行
test("sequential test", () => {
  expect(1 + 1).toBe(2);
});

test.serial

即使启用 --concurrent 标志,也强制测试顺序执行:

math.test.ts
import { test, expect } from "bun:test";

let sharedState = 0;

// 这些测试必须顺序运行
test.serial("first serial test", () => {
  sharedState = 1;
  expect(sharedState).toBe(1);
});

test.serial("second serial test", () => {
  // 依赖前一个测试
  expect(sharedState).toBe(1);
  sharedState = 2;
});

// 如果启用 --concurrent,此测试可并行执行
test("independent test", () => {
  expect(true).toBe(true);
});

// 测试限定符链式调用
test.failing.each([1, 2, 3])("chained qualifiers %d", input => {
  expect(input).toBe(0); // 对每个输入,该测试预期失败
});

重试失败的测试

使用 --retry 标志自动重试失败的测试,最多重试指定次数。如果测试失败后在后续尝试中通过,Bun 会将其报告为通过。

terminal
bun test --retry 3

每个测试可以通过 { retry: N } 设置覆盖全局的 --retry 值:

TypeScript
// 使用全局 --retry 值
test("uses global retry", () => {
  /* ... */
});

// 使用自己的重试次数覆盖 --retry
test("custom retry", { retry: 1 }, () => {
  /* ... */
});

你也可以在 bunfig.toml 中设置:

bunfig.toml
[test]
retry = 3

重新运行测试

使用 --rerun-each 标志多次运行每个测试。这有助于发现不稳定或非确定性的测试失败。

terminal
bun test --rerun-each 100

随机化测试执行顺序

使用 --randomize 标志以随机顺序运行测试,有助于发现依赖共享状态或执行顺序的测试。

terminal
bun test --randomize

使用 --randomize 时,Bun 会在测试摘要中显示用于随机化的种子:

terminal
bun test --randomize
Text
# ... 测试输出 ...
 --seed=12345
 2 pass
 8 fail
Ran 10 tests across 2 files. [50.00ms]

使用 --seed 实现可复现的随机顺序

使用 --seed 标志指定随机化种子,以便在调试依赖执行顺序的失败测试时复现相同的测试顺序。

terminal
# 复现之前的随机运行顺序
bun test --seed 123456

--seed 标志会隐式启用 --randomize,因此无需同时指定这两个标志。相同的种子始终会产生相同的测试执行顺序。

失败即停止 --bail

使用 --bail 标志,在测试失败达到指定次数后中止测试运行。默认情况下,Bun 会运行所有测试并报告所有失败,但在 CI 中,提前停止测试并减少 CPU 使用量可能更为合适。

terminal
# 发生 1 次失败即停止
bun test --bail

# 发生 10 次失败即停止
bun test --bail=10

监视模式

与 bun run 类似,bun test 接受 --watch 标志,以监视更改并重新运行测试。

terminal
bun test --watch

生命周期钩子

Bun 支持以下生命周期钩子:

钩子描述
beforeAll所有测试开始前执行一次
beforeEach每个测试开始前执行
afterEach每个测试结束后执行
afterAll所有测试完成后执行一次

在测试文件中定义钩子,或在使用 --preload 标志预加载的单独文件中定义钩子。

terminal
bun test --preload ./setup.ts

请参阅生命周期。

模拟

使用 mock 函数创建模拟函数。

math.test.ts
import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());

test("random", () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});

或者使用 jest.fn();其行为完全相同。

math.test.ts
import { test, expect, mock } from "bun:test"; // [!code --]
import { test, expect, jest } from "bun:test"; // [!code ++]

const random = mock(() => Math.random()); // [!code --]
const random = jest.fn(() => Math.random()); // [!code ++]

请参阅模拟。

快照测试

bun test 支持快照测试。

math.test.ts
// toMatchSnapshot 示例用法
import { test, expect } from "bun:test";

test("snapshot", () => {
  expect({ a: 1 }).toMatchSnapshot();
});

要更新快照,请使用 --update-snapshots 标志。

terminal
bun test --update-snapshots

请参阅快照。

UI 与 DOM 测试

Bun 兼容流行的 UI 测试库:

请参阅 DOM 测试。

大型代码库

对于包含数千个测试文件的测试套件,bun test 提供了多个可组合使用的调优选项:工作进程、隔离级别、跨机器分片,以及基于运行时长的调度。并行与隔离测试运行深入介绍了每一项。以下按大致收益顺序介绍它们的配合方式:

1. 使用每个核心:--parallel。 每个核心使用一个工作进程,文件一次分配一个。

2. 决定需要多少隔离。 --parallel 会为每个文件提供全新的全局环境,这是安全的默认设置,也是 Jest/Vitest 的做法。如果文件之间不会泄漏状态(它们在共享同一个全局环境的普通 bun test 下已经可以通过),--parallel --no-isolate 可让每个工作进程只评估一次导入和预加载脚本,而不是每个文件评估一次。对于由许多小文件组成的测试套件,这通常是最大的性能提升。请参阅对比方式。

3. 跨机器拆分:--shard=i/n。 确定性拆分,无需协调器。每个 CI 作业运行一个分片,而每个分片仍会在本地使用 --parallel。

4. 按耗时而非数量均衡:--timings。 有了记录的耗时数据后,Bun 会拆分分片,使每个分片的总耗时大致相同。拆分采用最长处理时间优先的方式,同时将路径相邻的文件放在一起,以保持工作进程的模块缓存处于预热状态。每个工作进程会先启动耗时最长的文件,空闲工作进程则会接手剩余文件中耗时最长的文件。这样,偶然最后启动的某个长文件就不会拖慢整个运行过程。

5. 自动保持耗时数据最新:--update-timings。 每个分片会写入其运行文件的耗时;下一次运行会读取所有这些数据。在 GitHub Actions 中,看起来如下:

.github/workflows/test.yml
jobs:
  test:
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      # last successful run's per-shard timings (nothing on the very first run)
      - uses: actions/cache/restore@v4
        with:
          path: .bun-test-timings
          key: bun-test-timings-${{ github.run_id }}
          restore-keys: bun-test-timings-
      - run: |
          bun test --parallel --shard=${{ matrix.shard }}/4 --update-timings \
            --timings=.bun-test-timings/next/${{ matrix.shard }}.json \
            $(ls .bun-test-timings/*.json 2>/dev/null | sed 's/^/--timings=/')
      - uses: actions/upload-artifact@v4
        with:
          name: timings-${{ matrix.shard }}
          path: .bun-test-timings/next/${{ matrix.shard }}.json
  save-timings:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: timings-*
          path: .bun-test-timings
          merge-multiple: true
      - uses: actions/cache/save@v4
        with:
          path: .bun-test-timings
          key: bun-test-timings-${{ github.run_id }}

每个分片必须读取相同的一组耗时文件,这样各分片的耗时才能汇总为整个测试套件的耗时。因此,一次运行会读取上次运行的文件(从缓存中恢复),并将自身的文件写入其他仍在运行的同级分片无法读取的位置(如上方的 next/)。如果第 2 步适用于你,请在 bun test 命令中添加 --no-isolate。

6. 在文件内部:test.concurrent 适用于将大部分时间花在等待上的 I/O 密集型测试。

性能

Bun 的测试运行器非常快速。

运行 266 个 React SSR 测试,速度快于 Jest 打印版本号。

AI 代理集成

当你将 Bun 的测试运行器与 AI 编程助手结合使用时,可以启用更安静的输出,在保留失败详情的同时去除其余噪音。

环境变量

设置以下任一环境变量即可启用适合 AI 的输出:

  • CLAUDECODE=1 - 适用于 Claude Code
  • REPL_ID=1 - 适用于 Replit
  • AGENT=1 - 通用 AI 代理标志

行为

当 Bun 检测到 AI 代理环境时:

  • 只详细显示测试失败信息
  • 隐藏通过、跳过及待办测试指示符
  • 保留统计摘要
terminal
# 示例:为 Claude Code 启用安静输出
CLAUDECODE=1 bun test

# 仍显示失败和摘要,隐藏通过测试的详细输出

CLI 用法

bash
bun test <patterns>

执行控制

--timeoutnumberdefault 5000

设置每个测试的超时时间,单位为毫秒(默认 5000)

--rerun-eachnumber

重新运行每个测试文件 NUMBER 次,以帮助发现某些错误

--retrynumber

最多重试失败的测试 NUMBER 次。每个测试的 { retry: N } 会覆盖此标志

--concurrentboolean

将所有测试视为 test.concurrent() 测试

--randomizeboolean

以随机顺序运行测试

--seednumber

设置测试随机化的随机种子

--bailnumber

失败 NUMBER 次后退出测试套件。如果不指定数字,则默认为 1

--max-concurrencynumberdefault 20

最大同时执行的测试数量(默认 20)

测试过滤

--todoboolean

包含标记为 test.todo() 的测试

--test-name-patternstring

只运行名称匹配给定正则表达式的测试。别名:-t

报告

--reporterstring

测试输出报告格式。可用选项:junit(需要 --reporter-outfile)、dots。默认:控制台输出。

--reporter-outfilestring

报告格式的输出文件路径(与 --reporter=junit 一起使用时必需)

--dotsboolean

启用点状报告。--reporter=dots 的简写

覆盖率

--coverageboolean

生成覆盖率报告

--coverage-reporterstringdefault text

以 text 和/或 lcov 格式报告覆盖率。默认 text

--coverage-dirstringdefault coverage

覆盖率文件的目录。默认 coverage

快照

--update-snapshotsboolean

更新快照文件。别名:-u

示例

运行所有测试文件:

terminal
bun test

运行所有文件名包含 "foo" 或 "bar" 的测试文件:

terminal
bun test foo bar

运行所有测试文件,仅包含名称中含有 "baz" 的测试:

terminal
bun test --test-name-pattern baz