使用node内置test runner,和 Jest say 拜拜

news2024/11/17 16:45:30

参考 https://nodejs.org/dist/latest-v20.x/docs/api/test.html#test-runner

在之前,我们写单元测试,必须安装第三方依赖包,而从node 20.0.0 版本之后,可以告别繁琐的第三方依赖包啦,可直接使用node的内置test runner。相关功能在20.0.0版本后才稳定,要使用test runner请安装node版本>=20.0.0
在这里插入图片描述

使用:

import test from 'node:test';

注意必须使用node:test,下面的代码将不会生效:

import test from 'test';

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

  1. 同步函数,如果抛出异常则认为失败,否则认为通过。
  2. 返回Promise的函数,如果Promise rejects,则认为失败,如果resolves则认为通过。
  3. 接收回调函数的函数,如果回调函数的第一个参数是true,则认为失败,如果是false,则认为通过。如果测试函数接收的回调函数也返回Promise,则测试将失败。

示例:

  1. 同步函数,未抛出异常,测试通过
test('synchronous passing test', (t) => {
  assert.strictEqual(1, 1);
});
  1. 同步函数,抛出异常,测试失败
test('synchronous failing test', (t) => {
  assert.strictEqual(1, 2);
});
  1. 返回Promise的函数,Promise resolves,测试通过
test('asynchronous passing test', async (t) => {
  assert.strictEqual(1, 1);
});
  1. 返回Promise的函数,Promise rejected,测试失败
test('asynchronous failing test', async (t) => {
  assert.strictEqual(1, 2);
});
  1. 直接使用Promise rejected,测试失败
test('failing test using Promises', (t) => {
  return new Promise((resolve, reject) => {
    setImmediate(() => {
      reject(new Error('this will cause the test to fail'));
    });
  });
});
  1. 使用setImmediate()调用done(),不传参
test('callback passing test', (t, done) => {
  setImmediate(done);
});
  1. 使用setImmediate()调用done(),传参Error对象,测试失败。
test('callback failing test', (t, done) => {
  setImmediate(() => {
    done(new Error('callback failure'));
  });
});

如果任何测试失败,进程退出码会被设置为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()方法跳过单个测试,如下面的示例所示。

  1. 跳过该测试选项,但不提供任何提示。
test('skip option', { skip: true }, (t) => {
 // 这里的代码不会执行
});
  1. 跳过该测试选项,提供提示。
test('skip option with message', { skip: 'this is skipped' }, (t) => {
  // 这里的代码不会执行
});
  1. 如果测试包含额外的逻辑,请确保返回到正确的位置。
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';

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”属性更新测试上下文,只运行子测试中有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', () => {
  // This code is not run.
  throw new Error('fail');
});

按名称筛选测试

--test-name-pattern命令行选项可用于仅运行名称与提供的模式匹配的测试。测试名字模式被解释为JavaScript正则表达式。可以多次指定--test-name-pattern选项,以便嵌套测试。对于执行的每个测试,也会运行任何相应的测试钩子,例如beforeEach()

给定以下测试文件,使用--test-name-pattern="test[1-3]"选项启动Node.js将导致测试运行器执行test 1test 2test 3。如果test 1不匹配测试名称,那么它的子测试就算名称匹配也不会执行。同一组测试也可以通过多次传递--test-name-pattern来执行(例如--test-name-pattern="test 1"--test-name-pattern="test 2",等等)。

test('test 1', async (t) => {
  await t.test('test 2');
  await t.test('test 3');
});

test('Test 4', async (t) => {
  await t.test('Test 5');
  await t.test('test 6');
});

测试名模式也可以使用正则表达式字面量指定。这允许使用正则表达式标志。在前面的例子中,用--test-name-pattern="/test [4-5]/i"启动Node.js会匹配Test 4Test 5,因为这个模式是不区分大小写的。

测试名称模式不会更改测试运行器执行的文件集。

无关的异步活动

一旦测试函数执行完成,就会在保持测试顺序的同时尽可能快地报告结果。然而,测试函数有可能生成比测试本身更长久的异步活动。测试运行器处理这种类型的活动,但是不会为了适应它而延迟测试结果的报告。

在下面的示例中,一个测试完成时,两个setImmediate()操作仍然未执行。第一个setImmediate()尝试创建一个新的子测试。因为父测试已经完成并输出其结果,所以新的子测试立即被标记为失败,并稍后报告给<TestsStream>

第二个setImmediate()创建一个uncaughtException事件。来自已完成测试的uncaughtExceptionunhandledRejection事件被test模块标记为失败,并由<TestsStream>在顶层作为诊断警告报告。

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');
  });

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

从命令行运行测试

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

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.js, test.cjs, test.mjs
    • ^test-.+ -文件名以字符串'test-'开头,后跟一个或多个字符的文件。示例:test-example.jstest-another-example.mjs
    • +[.-_]test$ -文件名以.test-test_test结尾,前面有一个或多个字符的文件。示例:example.test.jsexample-test.cjs, example_test.mjs
    • Node.js理解的其他文件类型,如.node.json,不会被测试运行器自动执行,但如果在命令行上显式提供,则会得到支持。

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

node:test模块支持在测试期间通过顶级mock对象进行模拟。下面的示例创建一个监视函数,该函数将两个数字相加,然后使用spy来断言函数是否按预期调用。

import assert from 'node:assert';
import { mock, test } from 'node:test';

test('spies on a function', () => {
  const sum = mock.fn((a, b) => {
    return a + b;
  });

  assert.strictEqual(sum.mock.calls.length, 0);
  assert.strictEqual(sum(3, 4), 7);
  assert.strictEqual(sum.mock.calls.length, 1);

  const call = sum.mock.calls[0];
  assert.deepStrictEqual(call.arguments, [3, 4]);
  assert.strictEqual(call.result, 7);
  assert.strictEqual(call.error, undefined);

  // 重置全局跟踪的mock
  mock.reset();
});

同样的模拟功能也暴露在每个测试的TestContext对象上。下面的示例使用TestContext上公开的API在对象方法上创建一个spy。通过测试上下文进行模拟的好处是,一旦测试结束,测试运行器将自动恢复所有模拟的功能。

test('spies on an object method', (t) => {
  const number = {
    value: 5,
    add(a) {
      return this.value + a;
    },
  };

  t.mock.method(number, 'add');
  assert.strictEqual(number.add.mock.calls.length, 0);
  assert.strictEqual(number.add(3), 8);
  assert.strictEqual(number.add.mock.calls.length, 1);

  const call = number.add.mock.calls[0];

  assert.deepStrictEqual(call.arguments, [3]);
  assert.strictEqual(call.result, 8);
  assert.strictEqual(call.target, undefined);
  assert.strictEqual(call.this, number);
});

Timers

模拟计时器是软件测试中常用的一种技术,用于模拟和控制计时器的行为,例如setIntervalsetTimeout,而无需实际等待指定的时间间隔。

有关方法和特性的完整列表,请参考MockTimers类。

这允许开发人员为依赖时间的功能编写更可靠和可预测的测试。

下面的例子展示了如何模拟setTimeout。使用.enable(['setTimeout']);它将模拟node:timersnode:timers/promises模块中的setTimeout函数,以及node .js全局上下文中的setTimeout函数。

注意: 此API目前不支持import {setTimeout} from 'node:timers'等解构函数。

import assert from 'node:assert';
import { mock, test } from 'node:test';

test('mocks setTimeout to be executed synchronously without having to actually wait for it', () => {
  const fn = mock.fn();

  // 选择要模拟的内容
  mock.timers.enable(['setTimeout']);
  setTimeout(fn, 9999);
  assert.strictEqual(fn.mock.callCount(), 0);

  // 推进时间
  mock.timers.tick(9999);
  assert.strictEqual(fn.mock.callCount(), 1);

  // 重置全局跟踪的mock
  mock.timers.reset();

  // 如果调用reset mock instance,它也会重置计时器实例
  mock.reset();
});

同样的模拟功能也暴露在每个测试的TestContext对象的mock属性中。通过测试上下文进行模拟的好处是,一旦测试结束,测试运行器将自动恢复所有模拟计时器功能。

import assert from 'node:assert';
import { test } from 'node:test';

test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
  const fn = context.mock.fn();

  // 选择要模拟的内容
  context.mock.timers.enable(['setTimeout']);
  setTimeout(fn, 9999);
  assert.strictEqual(fn.mock.callCount(), 0);

  // 推进时间
  context.mock.timers.tick(9999);
  assert.strictEqual(fn.mock.callCount(), 1);
});

测试reporter

node:test模块支持传递--test-reporter标志,让测试运行器使用特定的reporter。

支持以下内置reporter:

  • tap : tap reporter 以TAP格式输出测试结果。
  • spec : spec reporter 以人类可读的格式输出测试结果。
  • dot: dot reporter 以紧凑的格式输出测试结果,其中通过的测试用.表示,失败的测试用X表示。

sdout是TTY时,默认情况下使用spec reporter。否则,默认使用tap reporter。

这些 reporter 的确切输出可能会在Node.js的不同版本之间发生变化,不应该以编程方式依赖于它们。如果需要对测试运行器的输出进行编程访问,请使用发出的事件。

reporter 可通过node:test/reports模块获得:

import { tap, spec, dot } from 'node:test/reporters';

自定义reporter

--test-reporter可以用来指定自定义 reporter 的路径。自定义 reporter 是一个导出stream.compose接受的值的模块。reporter 应该转换由发出的事件。

使用<stream.Transform>的自定义 reporter 的示例:

import { Transform } from 'node:stream';

const customReporter = new Transform({
  writableObjectMode: true,
  transform(event, encoding, callback) {
    switch (event.type) {
      case 'test:start':
        callback(null, `test ${event.data.name} started`);
        break;
      case 'test:pass':
        callback(null, `test ${event.data.name} passed`);
        break;
      case 'test:fail':
        callback(null, `test ${event.data.name} failed`);
        break;
      case 'test:plan':
        callback(null, 'test plan');
        break;
      case 'test:diagnostic':
        callback(null, event.data.message);
        break;
      case 'test:coverage': {
        const { totalLineCount } = event.data.summary.totals;
        callback(null, `total line count: ${totalLineCount}\n`);
        break;
      }
    }
  },
});

export default customReporter;

提供给--test-reporter的值应该是一个字符串,类似于JavaScript代码中import()中使用的字符串,或者是提供给--import的值。

多种reporter

可以多次指定--test-reporter标志,以多种格式报告测试结果。在这种情况下,需要使用--test-reporter-destination为每个报告程序指定一个目标。目标可以是stdoutstderr或文件路径。reporter和目标按照指定的顺序配对。

在下面的例子中,spec reporter 将输出到 stdout, dot reporter 将输出到file.txt:

node --test-reporter=spec --test-reporter=dot --test-reporter-destination=stdout --test-reporter-destination=file.txt

当指定了单个reporter时,除非显式地提供了目标,否则将默认为stdout

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/789263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【搜索引擎Solr】Apache Solr 神经搜索

Sease[1] 与 Alessandro Benedetti&#xff08;Apache Lucene/Solr PMC 成员和提交者&#xff09;和 Elia Porciani&#xff08;Sease 研发软件工程师&#xff09;共同为开源社区贡献了 Apache Solr 中神经搜索的第一个里程碑。 它依赖于 Apache Lucene 实现 [2] 进行 K-最近邻…

龙芯iTOP-2K1000开发板制作启动U盘

我们准备一个 U 盘&#xff08;最小不要小于 4G&#xff0c;最大不要大于 32G&#xff09;&#xff0c;U 盘有且只有一个分区&#xff0c;U 盘格式化成 FAT32 分区&#xff0c;&#xff0c;如不满足要求&#xff0c;请格式化您的 U 盘&#xff0c;准备完成如下图所示 格式化软…

推荐系统(十)用户行为序列建模-Pooling 路线

对推荐系统而言&#xff0c;准确捕捉用户兴趣是其面临的核心命题。不管是样本、特征还是模型结构等方面的优化&#xff0c;本质上做的事情都是在提高推荐系统对用户兴趣的捕捉能力&#xff0c;因此如何提高这种能力&#xff0c;对推荐效果的提升有重要作用&#xff0c;也是算法…

性能优化问题

提升首屏的加载速度&#xff0c;是前端性能优化中「最重要」的环节&#xff0c;这里笔者梳理出一些 常规且有效 的首屏优化建议 1、路由懒加载 SPA 项目&#xff0c;一个路由对应一个页面&#xff0c;如果不做处理&#xff0c;项目打包后&#xff0c;会把所有页面打包成一个文…

使用lua脚本操作redis

redis中实现事务有两种方法&#xff1a; 1.WATCH监视键的变动&#xff0c;然后MULTI开始事务&#xff0c;EXEC提交事务 WATCH key [key…]&#xff1a;监视一个或多个键&#xff0c;如果在事务执行之前被修改&#xff0c;则事务被打断。 MULTI&#xff1a;标记一个事务的开始。…

Redis原理篇(二)

Redis原理 Redis数据结构 Redis网络模型 RESP协议 Redis内存回收 Redis原理篇 一、原理篇-Redis数据结构 1.1 Redis数据结构-动态字符串 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不…

JVM理论(六)执行引擎--垃圾回收

概述 垃圾: 指的是在运行程序中没有任何指针指向的对象垃圾回收目的: 为了及时清理空间使得程序可以正常运行垃圾回收机制: JVM采取的是自动内存管理,即JVM负责对象的创建以及回收,将程序员从繁重的内存管理释放出来,更加专注业务的开发垃圾回收区域: 频繁收集Young区(新生代)…

【前端知识】React 基础巩固(三十二)——Redux的三大原则、使用流程及实践

React 基础巩固(三十二)——Redux的三大原则 一、Redux的三大原则 单一数据源 整个应用程序的state被存储在一颗object tree 中&#xff0c;并且这个object tree 只存储在一个store中&#xff1b;Redux并没有强制让我们不能创建多个Store&#xff0c;但是那样做不利于数据维护…

Java网络编程(一)基本网络概念

一、网络 网络(network) 是几乎可以实时相互发送和接收数据的计算机和其他设备的集合。网络通常用线缆连接&#xff0c;数据位转换为电磁波&#xff0c;通过线缆移动。不过&#xff0c;无线网络会通过无线电波传输数据&#xff0c;许多长距离的传输现在会用通过玻璃纤维发送可见…

全加器(多位)的实现

一&#xff0c;半加器 定义 半加器&#xff08;Half Adder&#xff09;是一种用于执行二进制数相加的简单逻辑电路。它可以将两个输入位的和&#xff08;Sum&#xff09;和进位&#xff08;Carry&#xff09;计算出来。 半加器有两个输入&#xff1a;A 和 B&#xff0c;分别代表…

【Unity学习笔记】AssetBundle

文章目录 什么是AB包&#xff1f;为什么使用AB包? 如何导出AB包AB包导出文件 如何使用AB包AB包的加载同步加载异步加载 AB包的卸载依赖加载 AB包资源管理器 什么是AB包&#xff1f; AssetBundle是Unity提供的一种用于存储资源的压缩集合&#xff0c;它可以存储任何一种Unity可…

分布式操作系统会不会是操作系统的终端形态?

昨天一位网友私信我&#xff0c;提出一个问题&#xff1a;“Laxcus分布式操作系统会不会是操作系统发展的终极形态&#xff1f;”。今天觉得有必要把这件事说一说&#xff0c;所以就忙里偷闲写下这篇文章。 咱们先说结论&#xff1a;是也不是&#xff0c;需要具体情况具…

shell 脚本通过 dumpsys SurfaceFlinger --latency 数据计算 FPS 和评价流畅度。

目录 前言&#xff1a; 开篇前述&#xff1a; 一、设计初衷 二、设定预期倒推查找解决方案 设计实现部分 一、确定数据来源原因&#xff08;dumpsys SurfaceFlinger --latency&#xff09; 二、根据需求确定计算规则 三、代码实现 四、监控数据可视化交互结果设计 前言…

uni-app个人中心

一. 介绍uni-app&#xff1a; uni-app 是基于Vue.js框架开发的一个跨平台移动应用开发框架&#xff0c;可以同时支持多个平台&#xff08;如iOS、Android、Web等&#xff09;的应用开发。采用了统一的语法和组件规范&#xff0c;可以大大简化跨平台开发的工作&#xff0c;提高…

Redis持久化 :rdb与aof的持久化操作

redis持久化&#xff1a;分别启用rdb和aof&#xff0c;并查看是否有对应文件生成 rdb&#xff1a; #save 秒钟 写操作次数 如果在设置时间内写入数据达到规定的次数&#xff0c;则产生一次快照 [rootlocalhost redis-stable]# vim /etc/redis.conf :/save #查找有save关键字的…

Oracle 多条记录根据某个字段获取相邻两条数据间的间隔天数,小于31天的记录都筛选出来

需求描述&#xff1a;在Oracle中 住院记录记录表为v_hospitalRecords&#xff0c;表中FIHDATE入院时间&#xff0c;FBIHID是住院号&#xff0c; 我想查询出每个患者在他们的所有住院记录中是否在一个月内再次入院(相邻的两条记录进行比较)&#xff0c;并且住院记录大于一的患者…

window10脚本转服务教程

先说下脚本/我们启动的一些三方服务转window本机服务目前我了解到的好处 一键设置开机自启、随用随启、延时自启解决一些服务类应用启动后会阻塞当前dos窗口导致桌面一直要开着的问题脚本化服务注册&#xff0c;方便管理&#xff0c;统一运维… 1. 实践涉及内容介绍 编写好的…

力扣刷题SQL-619. 只出现一次的最大数字

MyNumbers 表&#xff1a; ------------------- | Column Name | Type | ------------------- | num | int | ------------------- 这张表没有主键。可能包含重复数字。这张表的每一行都含有一个整数。 单一数字 是在 MyNumbers 表中只出现一次的数字。 请你编写一…

解决uview1.x使用i18n,props在切换语言的时候未及时修改视图的问题

操作流程&#xff0c;用u-modal举例 未修改的u-modal.vue props取消文案 props:{// 取消文案cancelText: {type: String,default: 取消}, } 在这里插入代码片需要修改成适配i18n的 u-modal.vue //跟着官方的this.$t(lang.intro)写法&#xff0c;不知道是我没引好还是怎么的&a…

TCP 协议【传输层协议】

文章目录 1. 简介1.1 TCP 协议是什么1.2 TCP 协议的作用1.3 什么是“面向连接” 2. 简述 TCP2.1 封装和解包2.2 TCP 报文格式2.3 什么是“面向字节流”2.4 通过 ACK 机制实现一定可靠性 3. 详述 TCP3.1 基本认识TCP 报头格式16 位源/目标端口号32 位序列号*32 位确认应答号4 位…