Node.js v18.6.0 文档


目录

test 测试#

中英对照

稳定性: 1 - 实验

源代码: lib/test.js

node:test 模块有助于创建以 TAP 格式报告结果的 JavaScript 测试。 要访问它:

import test from 'node:test';const test = require('node:test');

此模块仅在 node: 协议下可用。 以下将不起作用:

import test from 'test';const test = require('test');

通过 test 模块创建的测试由单个函数组成,该函数以三种方式之一进行处理:

  1. 同步的函数,如果抛出异常则认为失败,否则认为通过。
  2. 返回 Promise 的函数,如果 Promise 拒绝,则认为该函数失败,如果 Promise 解决,则认为该函数通过。
  3. 接收回调函数的函数。 如果回调接收到任何真值作为其第一个参数,则认为测试失败。 如果非真值作为第一个参数传给回调,则认为测试通过。 如果测试函数接收到回调函数并且还返回 Promise,则测试将失败。

以下示例说明了如何使用 test 模块编写测试。

test('synchronous passing test', (t) => {
  // 此测试通过了,因为它没有抛出异常。
  assert.strictEqual(1, 1);
});

test('synchronous failing test', (t) => {
  // 此测试失败,因为它抛出了异常。
  assert.strictEqual(1, 2);
});

test('asynchronous passing test', async (t) => {
  // 此测试通过了,
  // 因为异步函数返回的 Promise 没有被拒绝。
  assert.strictEqual(1, 1);
});

test('asynchronous failing test', async (t) => {
  // 此测试失败,
  // 因为异步函数返回的 Promise 被拒绝。
  assert.strictEqual(1, 2);
});

test('failing test using Promises', (t) => {
  // Promise 也可以直接使用。
  return new Promise((resolve, reject) => {
    setImmediate(() => {
      reject(new Error('this will cause the test to fail'));
    });
  });
});

test('callback passing test', (t, done) => {
  // done() 是回调函数。
  // 当 setImmediate() 运行时,它调用 done() 不带参数。
  setImmediate(done);
});

test('callback failing test', (t, done) => {
  // 当 setImmediate() 运行时,
  // 使用 Error 对象调用 done() 并且测试失败。
  setImmediate(() => {
    done(new Error('callback failure'));
  });
});

当测试文件执行时,TAP 被写入 Node.js 进程的标准输出。 此输出可以被任何理解 TAP 格式的测试工具解释。 如果任何测试失败,则进程退出代码设置为 1

子测试#

中英对照

测试上下文的 test() 方法允许创建子测试。 此方法的行为与顶层 test() 函数相同。 以下示例演示了如何创建具有两个子测试的顶层测试。

test('top level test', async (t) => {
  await t.test('subtest 1', (t) => {
    assert.strictEqual(1, 1);
  });

  await t.test('subtest 2', (t) => {
    assert.strictEqual(2, 2);
  });
});

在本示例中,await 用于确保两个子测试均已完成。 这是必要的,因为父测试不会等待子测试完成。 当父测试完成时仍然未完成的任何子测试将被取消并视为失败。 任何子测试失败都会导致父测试失败。

跳过测试#

中英对照

通过将 skip 选项传给测试,或调用测试上下文的 skip() 方法,可以跳过单个测试。 这两个选项都支持包括在 TAP 输出中显示的消息,如下例所示。

// 使用了跳过选项,但没有提供任何消息。
test('skip option', { skip: true }, (t) => {
  // 这段代码永远不会被执行。
});

// 使用了跳过选项,并提供了一条消息。
test('skip option with message', { skip: 'this is skipped' }, (t) => {
  // 这段代码永远不会被执行。
});

test('skip() method', (t) => {
  // 如果测试包含额外的逻辑,则务必返回这里。
  t.skip();
});

test('skip() method with message', (t) => {
  // 如果测试包含额外的逻辑,则务必返回这里。
  t.skip('this is skipped');
});

describe/it 语法#

中英对照

运行测试也可以使用 describe 来声明套件和 it 来声明测试。 套件用于将相关测试组织和分组在一起。 ittest 的别名,除了没有通过测试上下文,因为嵌套是使用套件完成的,如本例所示

describe('A thing', () => {
  it('should work', () => {
    assert.strictEqual(1, 1);
  });

  it('should be ok', () => {
    assert.strictEqual(2, 2);
  });

  describe('a nested thing', () => {
    it('should work', () => {
      assert.strictEqual(3, 3);
    });
  });
});

describeit 是从 node:test 模块导入的

import { describe, it } from 'node:test';const { describe, it } = require('node:test');

only 测试#

中英对照

如果 Node.js 使用 --test-only 命令行选项启动,则可以通过将 only 选项传给应该运行的测试来跳过除选定子集之外的所有顶层测试。 当运行带有 only 选项集的测试时,所有子测试也会运行。 测试上下文的 runOnly() 方法可用于在子测试级别实现相同的行为。

// 假设 Node.js 使用 --test-only 命令行选项运行。
// 设置了 'only' 选项,因此运行此测试。
test('this test is run', { only: true }, async (t) => {
  // 在此测试中,默认运行所有子测试。
  await t.test('running subtest');

  // 可以使用 'only' 选项更新测试上下文以运行子测试。
  t.runOnly(true);
  await t.test('this subtest is now skipped');
  await t.test('this subtest is run', { only: true });

  // 切换上下文以执行所有测试。
  t.runOnly(false);
  await t.test('this subtest is now run');

  // 显式地不要运行这些测试。
  await t.test('skipped subtest 3', { only: false });
  await t.test('skipped subtest 4', { skip: true });
});

// 未设置 'only' 选项,因此跳过此测试。
test('this test is not run', () => {
  // 此代码未运行。
  throw new Error('fail');
});

无关的异步活动#

中英对照

一旦测试函数完成执行,则 TAP 结果会尽快输出,同时保持测试的顺序。 但是,测试函数可能会生成比测试本身寿命更长的异步活动。 测试运行器处理此类活动,但不会延迟报告测试结果以适应它。

在下面的示例中,测试完成时仍然有两个 setImmediate() 操作未完成。 第一个 setImmediate() 尝试创建新的子测试。 因为父测试已经完成并输出结果,新的子测试立即被标记为失败,并在文件的 TAP 输出的顶层报告。

第二个 setImmediate() 创建了 uncaughtException 事件。 源自已完成测试的 uncaughtExceptionunhandledRejection 事件由 test 模块处理,并在文件的 TAP 输出的顶层报告为诊断警告。

test('a test that creates asynchronous activity', (t) => {
  setImmediate(() => {
    t.test('subtest that is created too late', (t) => {
      throw new Error('error1');
    });
  });

  setImmediate(() => {
    throw new Error('error2');
  });

  // 此行之后测试结束。
});

从命令行运行测试#

中英对照

可以通过传入 --test 标志从命令行调用 Node.js 测试运行程序:

node --test

默认情况下,Node.js 将递归搜索当前目录以查找匹配特定命名约定的 JavaScript 源文件。 匹配文件作为测试文件执行。 有关预期测试文件命名约定和行为的更多信息可以在测试运行器执行模型章节中找到。

或者,可以提供一个或多个路径作为 Node.js 命令的最终参数,如下所示。

node --test test1.js test2.mjs custom_test_dir/

在本例中,测试运行程序将执行文件 test1.jstest2.mjs。 测试运行器还将递归搜索 custom_test_dir/ 目录以查找要执行的测试文件。

测试运行器执行模型#

中英对照

当搜索要执行的测试文件时,测试运行器的行为如下:

  • 执行用户显式提供的任何文件。
  • 如果用户没有显式地指定任何路径,则递归搜索当前工作目录中指定的文件,如以下步骤所示。
  • 除非用户显式地提供,否则跳过 node_modules 目录。
  • 如果遇到名为 test 的目录,则测试运行程序将递归搜索所有 .js.cjs.mjs 文件。 所有这些文件都被视为测试文件,不需要匹配下面详述的特定命名约定。 这是为了适应将所有测试放在单个 test 目录中的项目。
  • 在所有其他目录中,匹配以下模式的 .js.cjs.mjs 文件被视为测试文件:
    • ^test$ - 基本名称为字符串 'test' 的文件。 示例:test.jstest.cjstest.mjs
    • ^test-.+ - 基本名称以字符串 'test-' 开头,后跟一个或多个字符的文件。 示例:test-example.jstest-another-example.mjs
    • .+[\.\-\_]test$ - 基本名称以 .test-test_test 结尾的文件,前面有一个或多个字符。 示例:example.test.jsexample-test.cjsexample_test.mjs
    • Node.js 理解的其他文件类型,例如 .node.json,不会由测试运行程序自动执行,但如果在命令行上显式地提供,则支持。

每个匹配的测试文件都在单独的子进程中执行。 如果子进程以退出代码 0 结束,则认为测试通过。 否则,认为测试失败。 测试文件必须是 Node.js 可执行文件,但不需要在内部使用 node:test 模块。

test([name][, options][, fn])#

中英对照

  • name <string> 测试的名称,报告测试结果时显示。 默认值: The name fn 的属性,如果 fn 没有名称,则为 '<anonymous>'
  • options <Object> 测试的配置选项。 支持以下属性:
    • concurrency <number> 可同时运行的测试数。 如果未指定,则子测试从其父测试继承此值。 默认值: 1
    • only <boolean> 如果为真,并且测试上下文配置为运行 only 测试,则将运行此测试。 否则跳过测试。 默认值: false
    • skip <boolean> | <string> 如果为真,则跳过测试。 如果提供了字符串,则该字符串将作为跳过测试的原因显示在测试结果中。 默认值: false
    • todo <boolean> | <string> 如果为真,则测试标记为 TODO。 如果提供了字符串,则该字符串会显示在测试结果中作为测试为 TODO 的原因。 默认值: false
  • fn <Function> | <AsyncFunction> 被测试的函数。 此函数的第一个参数是 TestContext 对象。 如果测试使用回调,则回调函数作为第二个参数传入。 默认值: 无操作的函数。
  • 返回: <Promise> 测试完成后使用 undefined 解决。

test() 函数是从 test 模块导入的值。 每次调用此函数都会在 TAP 输出中创建一个测试点。

传给 fn 参数的 TestContext 对象可用于执行与当前测试相关的操作。 示例包括跳过测试、添加额外的 TAP 诊断信息、或创建子测试。

test() 返回 Promise,一旦测试完成就解决。 返回值通常可以被顶层测试丢弃。 但是,应该使用子测试的返回值来防止父测试先完成并取消子测试,如下例所示。

test('top level test', async (t) => {
  // 如果在下一行删除了 'await',
  // 则以下子测试中的 setTimeout() 将导致它的父测试寿命超过其父测试。
  // 一旦父测试完成,则它将取消所有未完成的子测试。
  await t.test('longer running subtest', async (t) => {
    return new Promise((resolve, reject) => {
      setTimeout(resolve, 1000);
    });
  });
});

describe([name][, options][, fn])#

中英对照

  • name <string> 套件名称,报告测试结果时显示。 默认值: The name fn 的属性,如果 fn 没有名称,则为 '<anonymous>'
  • options <Object> 套件的配置选项。 支持与 test([name][, options][, fn]) 相同的选项
  • fn <Function> 套件下的函数。 声明所有子测试和子套件的同步函数。 默认值: 无操作的函数。
  • 返回: undefined

node:test 模块导入的 describe() 函数。 每次调用此函数都会在 TAP 输出中创建一个子测试和一个测试点。 调用顶层 describe 函数之后,所有顶层测试和套件都将执行

describe.skip([name][, options][, fn])#

中英对照

跳过套件的简写,与 describe([name], { skip: true }[, fn]) 相同。

describe.todo([name][, options][, fn])#

中英对照

将套件标记为 TODO 的简写,与 describe([name], { todo: true }[, fn]) 相同。

it([name][, options][, fn])#

中英对照

  • name <string> 测试的名称,报告测试结果时显示。 默认值: The name fn 的属性,如果 fn 没有名称,则为 '<anonymous>'
  • options <Object> 套件的配置选项。 支持与 test([name][, options][, fn]) 相同的选项。
  • fn <Function> | <AsyncFunction> 被测试的函数。 如果测试使用回调,则回调函数作为参数传入。 默认值: 无操作的函数。
  • 返回: undefined

it() 函数是从 node:test 模块导入的值。 每次调用此函数都会在 TAP 输出中创建一个测试点。

it.skip([name][, options][, fn])#

中英对照

跳过测试的简写,与 it([name], { skip: true }[, fn]) 相同。

it.todo([name][, options][, fn])#

中英对照

将测试标记为 TODO 的简写,与 it([name], { todo: true }[, fn]) 相同。

TestContext#

中英对照

TestContext 的实例被传给每个测试函数,以便与测试运行器交互。 但是,TestContext 构造函数没有作为 API 的一部分公开。

context.diagnostic(message)#

中英对照

  • message <string> 要显示为 TAP 诊断的消息。

此函数用于将 TAP 诊断写入输出。 任何诊断信息都包含在测试结果的末尾。 此函数不返回值。

test('top level test', (t) => {
  t.diagnostic('A diagnostic message');
});

context.runOnly(shouldRunOnlyTests)#

中英对照

  • shouldRunOnlyTests <boolean> 是否运行 only 测试。

如果 shouldRunOnlyTests 为真,则测试上下文将只运行设置了 only 选项的测试。 否则,将运行所有测试。 如果 Node.js 不是使用 --test-only 命令行选项启动的,则此函数是无操作的。

test('top level test', (t) => {
  // 可以将测试上下文设置为使用 'only' 选项运行子测试。
  t.runOnly(true);
  return Promise.all([
    t.test('this subtest is now skipped'),
    t.test('this subtest is run', { only: true }),
  ]);
});

context.skip([message])#

中英对照

  • message <string> 要在 TAP 输出中显示的可选跳过消息。

此函数使测试的输出指示测试已跳过。 如果提供了 message,则它将包含在 TAP 输出中。 调用 skip() 不会终止测试函数的执行。 此函数不返回值。

test('top level test', (t) => {
  // 如果测试包含额外的逻辑,则务必返回这里。
  t.skip('this is skipped');
});

context.todo([message])#

中英对照

  • message <string> 要在 TAP 输出中显示的可选 TODO 消息。

此函数将 TODO 指令添加到测试的输出中。 如果提供了 message,则它将包含在 TAP 输出中。 调用 todo() 不会终止测试函数的执行。 此函数不返回值。

test('top level test', (t) => {
  // 此测试标记为 `TODO`
  t.todo('this is a todo');
});

context.test([name][, options][, fn])#

中英对照

  • name <string> 子测试的名称,在报告测试结果时显示。 默认值: The name fn 的属性,如果 fn 没有名称,则为 '<anonymous>'
  • options <Object> 子测试的配置选项。 支持以下属性:
    • concurrency <number> 可同时运行的测试数。 如果未指定,则子测试从其父测试继承此值。 默认值: 1
    • only <boolean> 如果为真,并且测试上下文配置为运行 only 测试,则将运行此测试。 否则跳过测试。 默认值: false
    • skip <boolean> | <string> 如果为真,则跳过测试。 如果提供了字符串,则该字符串将作为跳过测试的原因显示在测试结果中。 默认值: false
    • todo <boolean> | <string> 如果为真,则测试标记为 TODO。 如果提供了字符串,则该字符串会显示在测试结果中作为测试为 TODO 的原因。 默认值: false
  • fn <Function> | <AsyncFunction> 被测试的函数。 此函数的第一个参数是 TestContext 对象。 如果测试使用回调,则回调函数作为第二个参数传入。 默认值: 无操作的函数。
  • 返回: <Promise> 测试完成后使用 undefined 解决。

此函数用于在当前测试下创建子测试。 此函数的行为方式与顶层的 test() 函数相同。

test('top level test', async (t) => {
  await t.test(
    'This is a subtest',
    { only: false, skip: false, concurrency: 1, todo: false },
    (t) => {
      assert.ok('some relevant assertion here');
    }
  );
});
返回顶部