close
  • 简体中文
  • Metadata

    Metadata 允许你为测试、套件和文件结果附加可 JSON 序列化的数据。内置 Reporter 默认不会打印这些数据;它主要面向自定义 Reporter、programmatic API、CI 集成、仪表盘、归属关系映射,以及 mutation testing runner 等外部工具。

    为测试添加 metadata

    在测试选项对象中传入 meta,即可设置测试用例的初始 metadata。

    import { expect, test } from '@rstest/core';
    
    test(
      'charges credit card',
      { meta: { owner: 'payments', feature: 'checkout' } },
      () => {
        expect(1 + 1).toBe(2);
      },
    );

    Reporter hooks 和 programmatic API 可以从 TestResult.meta 读取这些声明的 metadata。你可以在测试声明时定义 metadata,也可以在测试运行过程中通过 task.meta 修改或添加字段:

    test('records runtime data', ({ task }) => {
      task.meta = {
        durationBucket: 'fast',
      };
    });

    为套件添加 metadata

    当多个测试共享相同字段时,可以把 meta 传给 describe

    import { describe, expect, test } from '@rstest/core';
    
    describe(
      'checkout',
      { meta: { owner: 'payments', layer: 'integration' } },
      () => {
        test('creates an order', ({ task }) => {
          // task.meta 是 { owner: 'payments', layer: 'integration' }
          expect(task.meta.owner).toBe('payments');
        });
    
        test(
          'uses coupon',
          { meta: { layer: 'e2e', caseId: 'PAY-456' } },
          ({ task }) => {
            // task.meta 是 { owner: 'payments', layer: 'e2e', caseId: 'PAY-456' }
            expect(task.meta.layer).toBe('e2e');
          },
        );
      },
    );

    套件 metadata 会被子套件和测试继承。子级顶层字段会覆盖父级同名字段,因此单个测试只需要覆盖不同的字段。

    从 Hook 添加文件和套件 metadata

    生命周期 Hook 可以通过 ctx.meta 获取 metadata 对象。

    import { afterAll, describe, test } from '@rstest/core';
    
    afterAll((ctx) => {
      ctx.meta.fileTag = 'ci-shard-1';
    });
    
    describe('checkout', { meta: { owner: 'payments' } }, () => {
      afterAll((ctx) => {
        ctx.meta.suiteDurationBucket = 'slow';
      });
    
      test('creates an order', () => {
        // ...
      });
    });

    文件级 Hook 会写入 TestFileResult.metadescribe 内部的 Hook 会写入对应套件结果的 meta,并通过 onTestSuiteResult 传给自定义 Reporter。

    在自定义 Reporter 中读取 metadata

    自定义 Reporter 可以从 case、suite 和 file 结果中读取 metadata。

    import type { Reporter } from '@rstest/core';
    
    const metadataReporter: Reporter = {
      onTestCaseStart(test) {
        // 经过套件继承和测试级覆盖后的初始 metadata。
        console.log('case start', test.name, test.meta);
      },
    
      onTestCaseResult(result) {
        // 测试执行和 hooks 运行后的最终 metadata。
        console.log('case result', result.name, result.meta);
      },
    
      onTestSuiteResult(result) {
        console.log('suite result', result.name, result.meta);
      },
    
      onTestFileResult(result) {
        console.log('file result', result.testPath, result.meta);
      },
    };

    如果需要测试运行前声明的 metadata,使用 onTestCaseStart。如果需要运行时产生的最终 metadata,使用 onTestCaseResultonTestSuiteResultonTestFileResult

    从 programmatic API 读取 metadata

    runRstest 的结果中包含与 Reporter 相同的结果 metadata。

    import { runRstest } from '@rstest/core/api';
    
    const result = await runRstest({
      cwd: process.cwd(),
      inlineConfig: {
        include: ['src/**/*.test.ts'],
      },
    });
    
    for (const file of result.files) {
      console.log(file.testPath, file.meta);
    
      for (const test of file.results) {
        console.log(test.name, test.meta);
      }
    }

    当外部工具掌控 Node.js 进程,并希望运行 Rstest 后直接消费结构化结果,而不是解析终端输出时,这种方式很有用。

    Metadata 值类型

    Metadata 必须可 JSON 序列化。TaskMetaTaskMetaValue@rstest/core 导出:

    type TaskMeta = Record<string, TaskMetaValue>;
    
    type TaskMetaValue =
      | string
      | number
      | boolean
      | null
      | TaskMetaValue[]
      | { [key: string]: TaskMetaValue };

    不要在 metadata 中存储函数、类实例、symbol、BigInt 或大型对象。建议使用小型字符串、数字、布尔值、数组和普通对象。

    继承和隔离规则

    Rstest 在收集和上报 metadata 时遵循以下规则:

    • 套件 metadata 会被子套件和测试继承。
    • 子级顶层字段会覆盖父级顶层字段。
    • 继承的 metadata 会为每个子级复制,因此运行时修改不会泄漏到兄弟测试或兄弟套件。
    • 嵌套对象和数组会在继承时复制。
    • 在测试中修改 task.meta 会影响该测试最终的 TestResult.meta
    • 在文件级 Hook 中修改 ctx.meta 会影响 TestFileResult.meta;在套件 Hook 中修改它会影响该套件结果的 meta

    Skipped 和 todo 测试

    跳过和 todo 测试仍然会保留声明的 metadata。

    import { test } from '@rstest/core';
    
    test.skip(
      'blocked by upstream',
      { meta: { owner: 'payments', reason: 'upstream' } },
      () => {
        // 这个函数体不会运行。
      },
    );
    
    test.todo('add refund coverage', {
      meta: { owner: 'payments', tracking: 'PAY-789' },
    });

    因为这些测试不会执行,所以无法进行运行时修改,但 Reporter 仍然可以从 onTestCaseStartonTestCaseResult 读取它们声明的 metadata。

    什么时候使用 metadata

    常见使用场景包括:

    • 归属字段,例如 ownerteamfeature
    • 测试管理系统链接,例如 caseIdtracking
    • CI 仪表盘字段,例如 shard、environment 或 risk level。
    • 自定义 Reporter 输出,例如 JSON、Markdown 或内部仪表盘。
    • 需要把测试结果映射到源码分析的外部工具,例如 mutation testing runner。

    最佳实践

    • 保持值小且可 JSON 序列化。
    • 优先使用稳定的字段名,例如 ownerfeaturecaseIdtracking
    • 将共享字段放在 describe 上,只在单个测试上覆盖例外。
    • 只有当 metadata 依赖运行时发现的信息时,才在运行时写入。
    • 使用自定义 Reporter 或 programmatic API 消费 metadata;内置 Reporter 更适合人类可读的测试输出。

    相关 API