当前位置: 首页 > news >正文

全面掌握 Jest:从零开始的测试指南(上篇)

随着JavaScript在前后端开发中的广泛应用,测试已成为保证代码质量的关键环节。

为什么需要单元测试

在我们的开发过程中,经常需要定义一些算法函数,例如将接口返回的数据转换成UI组件所需的格式。为了校验这些算法函数的健壮性,部分开发同学可能会手动定义几个输入样本进行初步校验,一旦校验通过便不再深究。

然而,这样的做法可能会带来一些潜在的问题。首先,边界值的情况往往容易被忽视,导致校验不够全面,增加了系统出现故障的风险。其次,随着需求的变化和演进,算法函数可能需要进行优化和扩展。如果前期的校验工作不够彻底,不了解现有函数覆盖的具体场景,就可能导致在后续的修改中引入新的问题。

单元测试可以有效地解决上述问题。在定义算法函数时,同步创建单元测试文件,并将可能出现的各种场景逐一列举。如果单元测试未能通过,项目在编译时会直接报错,从而能够及时发现并针对性地解决问题。此外,当后续有新同学加入并需要扩展功能时,他们不仅需要在原有的单元测试基础上添加新的测试用例,还能确保新功能的正确性,同时保障原有功能的正常运行。

自定义测试逻辑

在开始使用工具来进行单元测试之前,我们可以先自定义一个工具函数供测试使用。

例如,我们有一个 add 函数,期望它能够正确计算两个数的和,并验证其结果是否符合预期。比如,我们希望验证 2 + 3的结果是否等于 5 ,可以使用 expect(add(2, 3)).toBe(5) 这样的代码来实现。为此,我们可以自行定义一个expect 函数,使其具备类似Jest中 expect 函数的功能

function add(a, b) { return a + b; }
function expect(result) {return {toBe(value) {if (result === value) {console.log("验证成功");} else {throw new Error(`执行错误:${result} !== ${value}`);}},};
}// 调用示例
try {expect(add(2, 3)).toBe(5);  // 输出:"验证成功"expect(add(2, 3)).toBe(6);  // 抛出错误
} catch (err) {console.error(err.message);  // 输出:"执行错误:5 !== 6"
}

为了使测试更具描述性和可读性,我们可以进一步增强我们的测试逻辑。例如,我们可以添加一个 test 函数,用于描述测试的目的,并在测试失败时提供更详细的错误信息。

function test(description, fn) {try {fn();console.log(`测试通过: ${description}`);} catch (err) {console.error(`测试失败: ${description} - ${err.message}`);}
}
// 调用示例
test("验证 2 + 3 是否等于 5", () => {expect(add(2, 3)).toBe(5);
});
test("验证 2 + 3 是否等于 6", () => {expect(add(2, 3)).toBe(6);
});

通过这种方式,我们模拟了一个简单的测试用例,其中 test 和 expect 函数类似于Jest中的功能。然而,我们的自定义版本相对简陋,缺乏 Jest 提供的丰富功能。

Jest

通过上述示例,我们可以了解到编写测试的基本思路和方法。然而,在实际开发中,我们需要一个功能更加强大、易用性更高的测试工具。Jest 正是这样一个工具,它不仅提供了丰富的匹配器(如toBe、toEqual等),还支持异步测试Mock函数Snapshot测试 等功能。

引入 Jest 的依赖后,我们可以直接使用其内置的 test 和 expect 函数,从而大大提高测试的效率和准确性。Jest 的强大之处在于它能够帮助我们全面地覆盖各种测试场景,并提供详细的错误报告,使我们能够快速定位和解决问题。

初始化

首先,我们通过 npm install jest -D 安装 Jest 依赖,然后执行 npx jest --init。此时,命令行工具会出现一系列交互式问答,询问你是否要为 Jest 添加名为 test 的脚本指令、是否使用 TypeScript 作为配置文件、测试用例执行环境、是否需要代码覆盖率测试报告、生成测试报告的平台的编译器以及是否需要在每次测试用例执行前重置 Mock 函数状态。

完成所有问答后,Jest 会修改 package.json 文件,并生成jest.config.js配置文件。在执行测试用例时,将依据这些配置项进行。

我们创建一个 math.test.js 文件,并将之前的测试代码放入其中

function add(a, b) {return a + b;
}
test("测试 add 函数", () => {expect(add(2, 3)).toBe(5);
});

通过 npm run test 执行 Jest 运行指令,可以在命令行工具查看详细的测试信息,包括哪个文件的哪条测试用例的状态,以及简易的测试覆盖率报告。

在实际使用场景中,add 函数通常定义在项目文件中,并通过ES 模块化 (export 和 import) 方式导出和导入。默认情况下,Jest 并不支持 ES 模块化语法,因此我们需要通过 Babel 进行配置。

首先,执行以下命令安装 Babel 及其核心库和预设

npm install @babel/core @babel/preset-env --save-dev

然后,创建babel.config.js文件并定义配置

module.exports = {presets: [["@babel/preset-env",{targets: {node: "current",},},],],
};

接着,将 add 函数移到 math.js 文件中,并使用 export 导出

// math.js
export function add(a, b) {return a + b;
}

最后,在 math.test.js 文件中使用 import 导入

// math.test.js
import { add } from './math';
test("测试 add 函数", () => {expect(add(2, 3)).toBe(5);
});

通过以上步骤,你就完成了使用 Jest 执行 ES 模块化代码的环境初始化。

匹配器

Jest 中最常用的功能之一就是匹配器。在前面进行测试时,我们就接触过 toBe 这一匹配器,它用于判断值是否相等。除此之外,还有许多其他类型的匹配器。

值相等

判断值相等有两种匹配器:toBe 和 toEqual。对于基本数据类型(如字符串、数字、布尔值),两者的使用效果相同。但对于引用类型(如对象和数组),toBe 只有在两个引用指向同一个内存地址时才会返回 true

const user = { name: "alice" };
const info = { name: "alice" };test("toEqual", () => {expect(info).toEqual(user); // 通过,两者结构相同
});
test("toBe", () => {expect(info).toBe(user); // 不通过,两者的引用地址不同
});

是否有值

存在 toBeNulltoBeUndefined 和 toBeDefined 匹配器来分别判断值是否为 null、未定义或已定义。

test("toBeNull", () => {expect(null).toBeNull();expect(0).toBeNull(); // 不通过expect("hello").toBeNull(); // 不通过expect(undefined).toBeBull(); // 不通过
});test("toBeUnDefined", () => {expect(null).toBeUndefined(); // 不通过expect(0).toBeUndefined(); // 不通过expect("hello").toBeUndefined(); // 不通过expect(undefined).toBeUndefined();
});test("toBeDefined", () => {expect(null).toBeDefined();expect(0).toBeDefined();expect("hello").toBeDefined();expect(undefined).toBeDefined(); // 不通过
});

是否为真

toBeTruthy 用于判断值是否为真,toBeFalsy 用于判断值是否为假,not 用于取反。

test("toBeTruthy", () => {expect(null).toBeTruthy(); // 不通过expect(0).toBeTruthy(); // 不通过expect(1).toBeTruthy();expect("").toBeTruthy(); // 不通过expect("hello").toBeTruthy();expect(undefined).toBeTruthy(); // 不通过
});
test("toBeFalsy", () => {expect(null).toBeFalsy();expect(0).toBeFalsy();expect(1).toBeFalsy(); // 不通过expect("").toBeFalsy();expect("hello").toBeFalsy(); // 不通过expect(undefined).toBeFalsy();
});
test("not", () => {expect(null).not.toBeTruthy();expect("hello").not.toBeTruthy(); // 不通过
});

数字比较

toBeGreaterThan 用于判断是否大于某个数值,toBeLessThan 用于判断是否小于某个数值,toBeGreaterThanOrEqual 用于判断是否大于或等于某个数值,toBeCloseTo 用于判断是否接近某个数值(差值 < 0.005)。

test("toBeGreaterThan", () => {expect(9).toBeGreaterThan(5);expect(5).toBeGreaterThan(5); // 不通过expect(1).toBeGreaterThan(5); // 不通过
});test("toBeLessThan", () => {expect(9).toBeLessThan(5); // 不通过expect(5).toBeLessThan(5); // 不通过expect(1).toBeLessThan(5);
});test("toBeGreaterThanOrEqual", () => {expect(9).toBeGreaterThanOrEqual(5);expect(5).toBeGreaterThanOrEqual(5);expect(1).toBeGreaterThanOrEqual(5); // 不通过
});test("toBeCloseTo", () => {expect(0.1 + 0.2).toBeCloseTo(0.3);expect(1 + 2).toBeCloseTo(3);expect(0.1 + 0.2).toBeCloseTo(0.4); // 不通过
});

字符串相关

toMatch 用于判断字符串是否包含指定子字符串,部分包含即可。

test("toMatch", () => {expect("alice").toMatch("alice"); // 通过expect("alice").toMatch("lice"); // 通过expect("alice").toMatch("al"); // 通过
});

数组相关

toContain 用于判断数组是否包含指定元素,类似于 JavaScript 中的 includes 方法。

test("toContain", () => {expect(['banana', 'apple', 'orange']).toContain("apple");expect(['banana', 'apple', 'orange']).toContain("app"); // 不通过
});

error相关

toThrow 用于判断函数是否抛出异常,并可以指定抛出异常的具体内容。

test("toThrow", () => {const throwNewErrorFunc = () => {throw new TypeError("this is a new error");};expect(throwNewErrorFunc).toThrow();expect(throwNewErrorFunc).toThrow("new error");expect(throwNewErrorFunc).toThrow("TypeError"); // 不通过
});

以上就是各类型常用的匹配器。

命令行工具

在 package.json 中配置 script 指令,可以使 .test.js 文件在修改时实时自动执行测试用例。

"scripts": {"jest": "jest --watchAll"
},

在命令行中,你会实时看到当前测试用例的执行结果。同时,Jest 还提供了一些快捷配置,按下 w 键即可查看具体有哪些指令。

主要有以下几种类型:

f 模式在所有测试用例中,只执行上一次失败的测试用例。即使其他测试用例的内容有修改,也不会被执行。

o 模式只执行修改过的测试用例。这个功能需要配合 Git 来实现,根据本次相对于上次 Git 仓库的更改。这种模式还可以通过配置 script 指令来实现,即:

"script": {
"test": "jest --watch"
}

p模式当使用--watchAll 时,修改一个文件的代码后,所有的测试用例都会执行。进入 p 模式后,可以输入文件名 matchersFile,此时修改任何文件只会去查找包含 matchersFile 的文件并执行。

t模式输入测试用例名称,匹配 test 函数的第一个参数。匹配成功后即执行该测试用例。

q模式退出实时代码检测。

通过不同的指令,你可以更有针对性地检测测试用例。

钩子函数

在 Jest 中,describe 函数用于将一系列相关的测试用例(tests)组合在一起,形成一个描述性的测试块。它接受两个参数:第一个参数是一个字符串,用于描述测试块的主题;第二个参数是一个函数,包含一组测试用例。

即使没有显式定义 describe 函数,每个测试文件也会在最外层默认加上一层 describe 包裹。

在 describe 组成的每个块中,存在一些钩子函数,贯穿测试用例的整个过程。这些钩子函数主要用于测试用例执行之前的准备工作或之后的清理工作。

常用的钩子函数

  • beforeAll 函数在一个 describe 块开始之前执行一次

  • afterAll 函数在一个 describe 块结束之后执行一次

  • beforeEach 函数在每个测试用例之前执行

  • afterEach 在每个测试用例之后执行

示例代码

下面的示例代码展示了如何使用这些钩子函数:

describe("测试是否有值", () => {beforeAll(() => {console.log("beforeAll");});afterAll(() => {console.log("afterAll");});beforeEach(() => {console.log("beforeEach");});describe("toBeNull", () => {beforeAll(() => {console.log("toBeNull beforeAll");});afterAll(() => {console.log("toBeNull afterAll");});beforeEach(() => {console.log("toBeNull beforeEach");});test("toBeNull", () => {expect(null).toBeNull();});});
});

输出顺序

当运行上述测试用例时,输出的顺序如下:

beforeAll
toBeNull beforeAll
beforeEach
toBeNull beforeEach
toBeNull afterAll
afterAll

通过使用这些钩子函数,你可以更好地管理测试用例的生命周期,确保每次测试都从一个干净的状态开始,并在测试结束后清理掉产生的副作用。

在这一篇测试指南中,我们介绍了Jest 的背景、如何初始化项目、常用的匹配器语法、钩子函数。下一篇篇将继续深入探讨 Jest 的高级特性,包括 Mock 函数、异步请求的处理、Mock 请求的模拟、类的模拟以及定时器的模拟、snapshot 的使用。通过这些技术,我们将能够更高效地编写和维护测试用例,尤其是在处理复杂异步逻辑和外部依赖时。

文章转载自:一颗冰淇淋

原文链接:https://www.cnblogs.com/vigourice/p/18416692

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

相关文章:

全面掌握 Jest:从零开始的测试指南(上篇)

随着JavaScript在前后端开发中的广泛应用&#xff0c;测试已成为保证代码质量的关键环节。 为什么需要单元测试 在我们的开发过程中&#xff0c;经常需要定义一些算法函数&#xff0c;例如将接口返回的数据转换成UI组件所需的格式。为了校验这些算法函数的健壮性&#xff0c;部…...

Go 交叉编译

Mac 下编译 Linux 和 Windows 64位可执行程序 Linux&#xff1a; CGO_ENABLED0 GOOSlinux GOARCHamd64 go build main.go Windows&#xff1a; CGO_ENABLED0 GOOSwindows GOARCHamd64 go build main.go Linux 下编译 Mac 和 Windows 64位可执行程序 Mac: CGO_ENABLED0 G…...

goctl安装失败

今天遇到一个很奇怪的问题 在阿里云的ubuntu服务器上远程安装goctl&#xff1a;go install github.com/zeromicro/go-zero/tools/goctllatest&#xff0c;后面会断开ssh连接&#xff0c;就再也连不上了&#xff0c;connecting with ssh timed out。在阿里云的workbench上连接显…...

DebateGPT:通过多智能体辩论监督微调大模型

人工智能咨询培训老师叶梓 转载标明出处 这些模型的训练通常依赖于资源密集型的人工反馈&#xff0c;这不仅成本高昂&#xff0c;而且耗时。为了解决这一问题&#xff0c;一篇名为《FINE-TUNING LARGE LANGUAGE MODELS WITH MULTI-AGENT DEBATE SUPERVISION》的论文提出了一种…...

【最新综述】基于深度学习的超声自动无损检测(下)

4.Levels of automation 5.Basic axioms for DL-based ultrasonic NDE 在回顾了最新技术和每个自动化级别的贡献之后&#xff0c;我们不难发现&#xff0c;目前的数字语言方法论在不同论文之间存在着很大的差异。例如&#xff0c;有些作者提出了同时处理不同步骤的模型[121]&…...

kali——tshark的使用

目录 前言 使用方法 tshark提取流量为文档 前言 tshark 是一个命令行的网络分析工具&#xff0c;它用于捕获和分析网络流量。它支持多种网络协议&#xff0c;包括 TCP、UDP、ICMP 等。Tshark 可以用于调试网络问题、进行安全审计、分析应用程序性能等。 在 Kali Linux 中&…...

TortoiseSVN图标不显示的解决

解决办法一:修改svn软件的图标设置 1、选中一个文件夹或在桌面空白处,右击进入svn的setting 2、进入setting->Icon Overlays,Status cache选择Default或shell,然后点击应用 3、查看文件,图标可以正常显示 解决办法二:修改注册表的文件夹顺序 问题现象: 1、svn一直…...

Oracle 11gR2打PSU补丁详细教程

1 说明 Oracle的PSU&#xff08;Patch Set Update&#xff09;补丁是Oracle公司为了其数据库产品定期发布的更新包&#xff0c;通常每季度发布一次。PSU包含了该季度内收集的一系列安全更新&#xff08;CPU&#xff1a;Critical Patch Update&#xff09;以及一些重要的错误修…...

2.4 卷积1

2.4 卷积1 2.4 卷积 在了解了系统及其脉冲响应之后&#xff0c;人们可能会想知道是否有一种方法可以通过任何给定的输入信号&#xff08;不仅仅是单位脉冲&#xff09;确定系统的输出信号。卷积就是这个问题的答案&#xff0c;前提是系统是线性且时不变的&#xff08;LTI&…...

OA项目值用户登入首页展示

1.什么是OA 办公自动化(Office Automation,简称OA)是将现代化办公和计算机技术结合起来的一种新型的办公方式。办公自动化没有统一的定义,凡是在传统的办公室中采用各种新技术、新机器、新设备从事办公业务,都属于办公自动化的领域。通过实现办公自动化,或者说实现数字化…...

如何关闭前端Chrome的debugger反调试

1、禁用浏览器断点 2. 把控制台独立一个窗口...

硬件工程师笔试面试——晶振

目录 13、晶振 13.1 基础 晶振原理图 晶振实物图 13.1.1 概念 13.1.2 工作原理 13.1.3 应用领域 13.1.4 产品类型 13.2 相关问题 13.2.1 晶振的工作原理是什么,它如何保证频率的稳定性? 13.2.2 在工业控制领域,晶振是如何确保精确度的? 13.2.3 晶振的Q值是如何…...

如何用安卓玩Java版Minecraft,安卓手机安装我的世界Java版游戏的教程

安卓手机使用FCL启动器安装我的世界Java版游戏的教程。如何用安卓玩Java版Minecraft 视频教程&#xff1a;https://www.bilibili.com/video/BV1CctYebEzR/ 前言 目前&#xff0c;安卓设备上可以用来运行Java版Minecraft的启动器主要有以下几款&#xff1a; PojavLauncher&a…...

linux上用yolov8训练自己的数据集(pycharm远程连接服务器)

pycharm如何远程连接服务器&#xff0c;看之前的文章 首先去GitHub上下载项目地址&#xff0c;然后下载预训练模型放到项目主目录下 然后下载数据集&#xff0c;我这有个推荐的数据集下载网站&#xff0c;可以直接下载yolov8格式的数据集&#xff08;还支持其他格式的数据集&a…...

Git rebase 的使用(结合图与案例)

目录 Git rebase 的使用Git rebase 概念Git rebase 原理rebase和merge的选择 Git rebase 的使用 在 Git 中整合来自不同分支的修改主要有两种方法&#xff1a;merge 以及 rebase Git rebase 概念 **rebase概念&#xff1a;**用来重新应用提交&#xff08;commits&#xff09…...

一文讲懂Mac中的环境变量

你是否曾经因为环境变量配置不当而浪费了宝贵的开发时间?你是否好奇为什么有时候在终端输入命令会提示"command not found",而有时候又能正常运行?如果你是一名Mac用户,并且希望真正掌握环境变量的奥秘,那么这篇文章将为你揭开Mac中环境变量的神秘面纱,帮助你成为一…...

将硬盘的GPT 转化为MBR格式

遇到的问题 在重新安装系统时&#xff0c;磁盘遇到无法空间分配给系统。 解决方式 使用Windows10镜像 U盘安装&#xff0c;选择磁盘时&#xff0c;转换磁盘格式为MBR。然后退出安装程序。 Shift F10# 输入 diskpart# 查看磁盘信息 list disk# 选择需要转换的磁盘&#xff0…...

C++基于select和epoll的TCP服务器

select版本 服务器 #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <string> #include <pthread.h> #include <sys/select…...

SpringBoot 读取配置文件的4种方式

文章目录 1. Value 注解读取单个属性2. 使用 ConfigurationProperties 注解3. 通过 Environment 对象读取属性4. 使用 PropertySource 注解加载额外的配置文件 在 Spring Boot 中&#xff0c;application.yml 文件用于配置应用程序的属性&#xff0c;Spring Boot 默认会从 src/…...

【车载开发系列】ParaSoft单元测试环境配置(三)

【车载开发系列】ParaSoft单元测试环境配置(三) 【车载开发系列】ParaSoft单元测试环境配置(三) 【车载开发系列】ParaSoft单元测试环境配置(三)一. 去插桩设置Step1:静态解析代码Step2:编辑Parasoft文件Step3:确认去插桩二. 新增测试用例Step1:生成测试用例Step2:执…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...