close
  • 简体中文
  • coverage

    • 类型:
    type CoverageOptions = {
      enabled?: boolean;
      provider?: 'istanbul' | 'v8';
      include?: string[];
      changed?: boolean | string;
      exclude?: string[];
      reporters?: CoverageReporter[];
      reportsDirectory?: string;
      reportOnFailure?: boolean;
      clean?: boolean;
      allowExternal?: boolean;
      thresholds?: CoverageThresholds;
    };
    
    type CoverageReporter = string | [string, Record<string, unknown>] | ReportBase;
    • 默认值: undefined

    收集测试覆盖率信息并生成覆盖率报告。

    $ npx rstest --coverage
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    ----------|---------|----------|---------|---------|-------------------
    All files |     100 |      100 |     100 |     100 |
     index.ts |     100 |      100 |     100 |     100 |
    ----------|---------|----------|---------|---------|-------------------

    选项

    enabled

    • 类型: boolean
    • 默认值: false
    • CLI: --coverage, --coverage=false, --no-coverage

    启用或禁用测试覆盖率收集。

    CLI
    rstest.config.ts
    npx rstest --coverage

    provider

    • 类型: 'istanbul' | 'v8'
    • 默认值: 'istanbul'
    • CLI: --coverage.provider <provider>

    选择覆盖率收集方式。Rstest 同时支持 istanbulv8。如果你更关注基于 SWC 插桩的转换性能和稳定的 Istanbul 语义,可以选择 istanbul;如果开启 coverage 后主要受内存压力影响,可以选择 v8,它通常有更低的运行时内存占用,因为不会为每个执行模块注入覆盖率计数器。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        provider: 'v8',
      },
    });

    Istanbul provider

    Istanbul 是一个广泛使用的 JavaScript 代码覆盖率分析工具,它通过插桩的方式来收集代码覆盖率信息。

    要启用 istanbul 覆盖率,需要先安装 @rstest/coverage-istanbul

    npm
    yarn
    pnpm
    bun
    deno
    npm add @rstest/coverage-istanbul -D

    @rstest/coverage-istanbulswc-plugin-coverage-instrument 提供支持。Rstest 会在 transform 阶段通过 SWC 完成插桩,因此该 provider 具有较好的 transform 性能。但插桩后的代码会在执行时保留用于收集覆盖率的计数器,因此运行时内存占用可能更高。

    序列化函数和其他 realm

    Istanbul 会向插桩后的文件注入 cov_* 覆盖率计数器调用。这些计数器的作用域属于 Rstest 执行的 transformed module。如果来自插桩文件的代码被序列化后在其他位置执行,新的 scope 或 realm 可能没有对应的 cov_* helper,测试就可能以 ReferenceError: cov_... is not defined 失败。

    当函数通过 fn.toString() 序列化,或者通过 page.evaluateWorkernode:vmevalnew Function 等 API 执行时,可能触发这个问题。要避免该错误,可以通过 coverage.exclude 排除对应源文件;在非 Browser Mode 测试中切换到 coverage.provider: 'v8';或者避免序列化经过 Istanbul 插桩的函数。v8 provider 在 Browser Mode 中不可用,因此浏览器测试应使用 coverage.exclude,或避免序列化经过插桩的函数。

    如果整个文件主要用于生成、序列化,或向其他 scope 或 realm 注入代码,优先使用 coverage.exclude。Istanbul ignore 注释更适合在一个仍需要收集覆盖率的文件中,跳过很小的独立片段。

    在配置中排除整个文件:

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        provider: 'istanbul',
        exclude: ['**/src/injected-script.ts', '**/src/**/*.{worker,evaluate}.ts'],
      },
    });

    也可以在临时执行时通过 CLI 传入相同的规则:

    npx rstest run --coverage --coverage.exclude "**/src/injected-script.ts" --coverage.exclude "**/src/**/*.{worker,evaluate}.ts"

    如果整个文件都不应被 Istanbul 插桩,可以在文件顶部添加 /* istanbul ignore file */

    src/injected-script.ts
    /* istanbul ignore file */
    
    export function createInjectedScript() {
      return `globalThis.__APP_READY__ = true;`;
    }

    如果只有一个会被序列化的函数需要跳过,可以把 /* istanbul ignore next */ 放在它前面:

    /* istanbul ignore next */
    function browserEvaluateFn() {
      return window.location.href;
    }
    
    await page.evaluate(browserEvaluateFn);

    对于很小的环境判断分支,也可以使用 /* istanbul ignore if *//* istanbul ignore else */。如果被忽略的片段里仍然出现了注入的 cov_* 调用,请改用 coverage.exclude 排除对应源文件。

    V8 provider

    v8 provider 基于 Node.js inspector precise coverage 收集数据,并重映射为 Istanbul 格式。相比 Istanbul 插桩,V8 coverage 通常有更低的运行时内存占用,因为执行模块中不需要额外的覆盖率计数器。它更适合大型非 Browser Mode 测试套件中 coverage 内存压力明显的场景。

    要启用 V8 覆盖率,需要先安装 @rstest/coverage-v8

    npm
    yarn
    pnpm
    bun
    deno
    npm add @rstest/coverage-v8 -D

    由于底层覆盖率数据来自 Node.js / V8 本身,重映射后的 branch 覆盖率和未覆盖行信息在不同 Node.js 或 V8 版本之间可能存在轻微差异。

    v8 provider 仅适用于非 Browser Mode 测试执行,包括 nodejsdomhappy-dom 测试环境。Browser Mode 不使用 inspector coverage,因此浏览器 coverage 应使用默认的 istanbul provider。

    include

    • 类型: string[]
    • 默认值: undefined
    • CLI: --coverage.include <pattern>

    对匹配 glob 规则的文件进行测试覆盖率收集。重复传入 CLI 参数可以指定多个模式。

    默认情况下,Rstest 会收集已测试文件的覆盖率。如果你希望在覆盖率报告中包含未测试的文件,可以使用 include 选项指定要包含的文件或模式。

    需要注意的是,这里应使用标准 glob 语法。匹配单个扩展名时,请写 src/**/*.ts,不要写 src/**/*.{ts}。底层 glob 库会把单元素花括号按字面量处理,因此 src/**/*.{ts} 不会匹配 .ts 文件。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        include: ['src/**/*.{js,jsx,ts,tsx}'],
      },
    });

    changed

    • 类型: boolean | string
    • 默认值: undefined
    • CLI: --coverage.changed, --coverage.changed=<commit>

    只收集当前 Git 仓库中变更文件的覆盖率。你也可以传入 commit 或 branch,收集从该 ref 以来变更文件的覆盖率。

    这个选项既可以写在配置里,也可以通过 CLI 使用:

    • 当你希望在项目里长期保持一个默认行为时,使用 rstest.config.ts 中的 coverage.changed,例如在 CI 中。
    • 当你只想临时跑一次时,使用 --coverage.changed

    coverage.changed 只控制覆盖率报告范围,不会改变要运行的测试。

    也就是说,下面两个命令的效果并不一样:

    npx rstest run --changed
    npx rstest run --coverage.changed
    • --changed 会改变测试选择范围,只运行与变更文件相关的测试。
    • --coverage.changed 会保持正常的测试选择范围,只把覆盖率报告限制在变更的源码文件上。

    --changed 的关系

    • 当使用 --changed 且没有配置 coverage.changed 时,覆盖率报告会继承 --changed 收集到的变更文件。
    • 如果 --changed 命中了 forceRerunTriggers,Rstest 会回退为运行完整测试套件。此时覆盖率默认也会回到完整范围,除非显式启用了 coverage.changed
    • 如果你想用 --changed 缩小测试执行范围,但仍然保留完整覆盖率报告,可以在配置中将 coverage.changed 设为 false

    常见用法如下:

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        changed: 'origin/main',
      },
    });

    上面的配置不会改变测试执行范围,但会让覆盖率报告只包含相对 origin/main 有变更的文件。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        changed: 'HEAD~1',
      },
    });
    npx rstest run --changed --coverage
    npx rstest run --coverage.changed
    npx rstest run --coverage.changed=HEAD~1
    npx rstest run --coverage.changed=origin/main
    • npx rstest run --changed --coverage 会运行变更相关测试,并且默认把覆盖率也限制到同一批变更文件。
    • npx rstest run --coverage.changed=HEAD~1 会执行正常测试集,但只报告相对 HEAD~1 以来变更文件的覆盖率。
    • npx rstest run --coverage.changed=origin/main 适合在功能分支上使用:它仍然会执行正常测试集,但覆盖率报告只包含相对 origin/main 变更过的文件。

    exclude

    • 类型: string[]
    • CLI: --coverage.exclude <pattern>
    • 默认值:
    [
      '**/node_modules/**',
      '**/__tests__/**',
      '**/__mocks__/**',
      '**/*.d.ts',
      '**/*.{test,spec}.[jt]s',
      '**/*.{test,spec}.[cm][jt]s',
      '**/*.{test,spec}.[jt]sx',
      '**/*.{test,spec}.[cm][jt]sx',
    ];

    匹配 glob 规则的文件将从测试覆盖率收集中排除。自定义配置将与默认值合并。

    Rstest 默认不会排除名为 test 的目录。这样可以避免误排除路径中包含 test 片段的 package 或 workspace 源文件,例如 packages/test/src/index.ts。如果你希望从覆盖率中排除这些目录,可以显式添加 test/****/test/** 等规则。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        exclude: ['**/node_modules/**', '**/dist/**'],
      },
    });

    reporters

    • 类型: CoverageReporter[]
    • 默认值: ['text', 'html', 'clover', 'json']
    • CLI: --coverage.reporters <reporter>

    用于覆盖率收集的报告器。Rstest 使用标准 Istanbul reporter API,而不是 Rstest 专属的 coverage reporter API。

    每个 reporter 可以是字符串(reporter 名称)、包含 reporter 名称及其选项的元组,也可以是带有 execute(context) 方法且兼容 Istanbul 的 reporter 对象。

    CLI 仅支持传入报告器名称。如需指定多个报告器,可以重复使用该参数:

    npx rstest run --coverage.reporters text --coverage.reporters=json
    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        reporters: [
          'html',
          ['text', { skipFull: true }],
          ['json', { file: 'coverage-final.json' }],
        ],
      },
    });

    自定义 coverage reporter

    自定义 coverage reporter 应遵循 Istanbul reporter 约定。reporter 包或文件会由 Rstest 的 coverage provider 加载(在可能的情况下会通过 istanbul-reports 创建),使用 coverage.reporters 中传入的选项创建实例,然后用 Istanbul report context 调用。

    例如,一个 ESM reporter 可以导出带有 execute 方法的 class。context 参数对应 istanbul-lib-report 中的 Istanbul Context 类型:

    custom-coverage-reporter.mjs
    export default class CustomCoverageReporter {
      constructor(options = {}) {
        this.options = options;
      }
    
      /** @param {import('istanbul-lib-report').Context} context */
      execute(context) {
        // Use the standard Istanbul report context.
        const summary = context.getTree('flat').getRoot().getCoverageSummary();
        console.log('Coverage summary:', summary.toJSON());
      }
    }

    然后在 rstest.config.ts 中通过包名或文件路径引用该 reporter:

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        reporters: [
          'text',
          ['./custom-coverage-reporter.mjs', { outputFile: 'summary.json' }],
        ],
      },
    });

    如果在配置中直接创建 reporter,也可以传入兼容 Istanbul 的 reporter 对象:

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        reporters: [
          {
            execute(context) {
              const summary = context
                .getTree('flat')
                .getRoot()
                .getCoverageSummary();
              console.log(summary.toJSON());
            },
          },
        ],
      },
    });

    reportsDirectory

    • 类型: string
    • 默认值: './coverage'
    • CLI: --coverage.reportsDirectory <dir>

    存储覆盖率报告的目录。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        reportsDirectory: './coverage-reports',
      },
    });

    reportOnFailure

    • 类型: boolean
    • 默认值: false
    • CLI: --coverage.reportOnFailure

    在测试失败时是否生成覆盖率报告并检查阈值。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    export default defineConfig({
      coverage: {
        enabled: true,
        reportOnFailure: true,
      },
    });

    allowExternal

    • 类型: boolean
    • 默认值: false
    • CLI: --coverage.allowExternal

    是否收集项目根目录之外的源文件的覆盖率。这在 monorepo 中非常有用,例如测试文件导入了来自同级工作区包的模块。

    默认情况下,Rstest 会从覆盖率报告中排除项目根目录之外的文件,这与 Jest 和 Vitest 的行为一致。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        allowExternal: true,
      },
    });

    clean

    • 类型: boolean
    • 默认值: true
    • CLI: --coverage.clean--coverage.clean=false

    是否在运行测试之前清理覆盖率目录。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        clean: true,
      },
    });

    thresholds

    • 类型:
    type CoverageThreshold = {
      statements?: number;
      functions?: number;
      branches?: number;
      lines?: number;
    };
    
    type CoverageThresholds = CoverageThreshold & {
      /** 为匹配的文件指定覆盖率阈值 */
      [glob: string]: CoverageThreshold;
    };
    • 默认值: undefined

    设置最低代码覆盖率要求。你可以为语句、函数、分支和行覆盖率设置阈值。

    当阈值设置为正数时,表示所需的最低百分比。当阈值设置为负数时,表示允许未覆盖的最大数量。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        thresholds: {
          statements: 80,
          functions: 80,
          branches: 80,
          lines: -10,
        },
      },
    });

    当代码覆盖率低于指定阈值时,测试将失败并输出如下错误信息:

    Error: Coverage for statements 75% does not meet global threshold 80%
    Error: Coverage for functions 75% does not meet global threshold 80%
    Error: Coverage for branches 75% does not meet global threshold 80%
    Error: Uncovered lines 20 exceeds maximum global threshold allowed 10

    glob 模式

    当指定 glob 模式时,Rstest 将根据匹配的文件模式进行代码覆盖率检查。如果指定的文件路径不存在,则返回错误。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        thresholds: {
          // 为匹配的文件指定覆盖率阈值
          'src/**': {
            statements: 100,
          },
          'node/**/*.js': {
            statements: 90,
          },
          // 指定所有文件的总阈值
          statements: 80,
        },
      },
    });

    根据以上配置,rstest 将在以下情况下失败:

    • src/** 匹配到的文件的总语句覆盖率低于 100%。
    • node/**/*.js 匹配到的文件的总语句覆盖率低于 90%。
    • 全局的语句覆盖率低于 80%。

    单文件检查

    Rstest 支持通过将 perFile 设置为 true 来为每个匹配文件分别检查阈值。

    rstest.config.ts
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      coverage: {
        enabled: true,
        thresholds: {
          'src/**': {
            statements: 90,
            perFile: true,
          },
        },
      },
    });