深入浅出 Jest 框架的实现原理

本文深入探讨了 Jest 框架的工作原理,包括测试块、断言、匹配器的实现,以及 CLI、配置、模拟、执行环境等方面。通过示例解释了如何创建和运行测试,展示了 Jest 如何通过注入全局方法、模拟函数、处理测试生命周期来支持测试。文章旨在帮助读者理解 Jest 的核心思想,以便更好地编写和维护测试代码。
摘要由CSDN通过智能技术生成

English Version | 中文版

深入浅出 Jest 框架的实现原理

https://github.com/Wscats/jest-tutorial

什么是 Jest

Jest 是 Facebook 开发的 Javascript 测试框架,用于创建、运行和编写测试的 JavaScript 库。

Jest 作为 NPM 包发布,可以安装并运行在任何 JavaScript 项目中。Jest 是目前前端最流行的测试库之一。

测试意味着什么

在技 ​​ 术术语中,测试意味着检查我们的代码是否满足某些期望。例如:一个名为求和(sum)函数应该返回给定一些运算结果的预期输出。

有许多类型的测试,很快你就会被术语淹没,但长话短说的测试分为三大类:

  • 单元测试
  • 集成测试
  • E2E 测试

我怎么知道要测试什么

在测试方面,即使是最简单的代码块也可能使初学者也可能会迷惑。最常见的问题是“我怎么知道要测试什么?”。

如果您正在编写网页,一个好的出发点是测试应用程序的每个页面和每个用户交互。但是网页其实也需要测试的函数和模块等代码单元组成。

大多数时候有两种情况:

  • 你继承遗留代码,其自带没有测试
  • 你必须凭空实现一个新功能

那该怎么办?对于这两种情况,你可以通过将测试视为:检查该函数是否产生预期结果。最典型的测试流程如下所示:

  • 导入要测试的函数
  • 给函数一个输入
  • 定义期望的输出
  • 检查函数是否产生预期的输出

一般,就这么简单。掌握以下核心思路,编写测试将不再可怕:

输入 -> 预期输出 -> 断言结果。

测试块,断言和匹配器

我们将创建一个简单的 Javascript 函数代码,用于 2 个数字的加法,并为其编写相应的基于 Jest 的测试

const sum = (a, b) => a + b;

现在,为了测试在同一个文件夹中创建一个测试文件,命名为 test.spec.js,这特殊的后缀是 Jest 的约定,用于查找所有的测试文件。我们还将导入被测函数,以便执行测试中的代码。Jest 测试遵循 BDD 风格的测试,每个测试都应该有一个主要的 test 测试块,并且可以有多个测试块,现在可以为 sum 方法编写测试块,这里我们编写一个测试来添加 2 个数字并验证预期结果。我们将提供数字为 1 和 2,并期望输出 3。

test 它需要两个参数:一个用于描述测试块的字符串,以及一个用于包装实际测试的回调函数。expect 包装目标函数,并结合匹配器 toBe 用于检查函数计算结果是否符合预期。

这是完整的测试:

test("sum test", () => {
   
  expect(sum(1, 2)).toBe(3);
});

我们观察上面代码有发现有两点:

  • test 块是单独的测试块,它拥有描述和划分范围的作用,即它代表我们要为该计算函数 sum 所编写测试的通用容器。
  • expect 是一个断言,该语句使用输入 1 和 2 调用被测函数中的 sum 方法,并期望输出 3。
  • toBe 是一个匹配器,用于检查期望值,如果不符合预期结果则应该抛出异常。

如何实现测试块

测试块其实并不复杂,最简单的实现不过如下,我们需要把测试包装实际测试的回调函数存起来,所以封装一个 dispatch 方法接收命令类型和回调函数:

const test = (name, fn) => {
   
  dispatch({
    type: "ADD_TEST", fn, name });
};

我们需要在全局创建一个 state 保存测试的回调函数,测试的回调函数使用一个数组存起来。

global["STATE_SYMBOL"] = {
   
  testBlock: [],
};

dispatch 方法此时只需要甄别对应的命令,并把测试的回调函数存进全局的 state 即可。

const dispatch = (event) => {
   
  const {
    fn, type, name } = event;
  switch (type) {
   
    case "ADD_TEST":
      const {
    testBlock } = global["STATE_SYMBOL"];
      testBlock.push({
    fn, name });
      break;
  }
};

如何实现断言和匹配器

断言库也实现也很简单,只需要封装一个函数暴露匹配器方法满足以下公式即可:

expect(A).toBe(B)

这里我们实现 toBe 这个常用的方法,当结果和预期不相等,抛出错误即可:

const expect = (actual) => ({
   
    toBe(expected) {
   
        if (actual !== expected) {
   
            throw new Error(`${
     actual} is not equal to ${
     expected}`);
        }
    }
};

实际在测试块中会使用 try/catch 捕获错误,并打印堆栈信息方面定位问题。

在简单情况下,我们也可以使用 Node 自带的 assert 模块进行断言,当然还有很多更复杂的断言方法,本质上原理都差不多。

CLI 和配置

编写完测试之后,我们则需要在命令行中输入命令运行单测,正常情况下,命令类似如下:

node jest xxx.spec.js

这里本质是解析命令行的参数。

const testPath = process.argv.slice(2)[0];
const code = fs.readFileSync(path.join(process.cwd(), testPath)).toString();

复杂的情况可能还需要读取本地的 Jest 配置文件的参数来更改执行环境等,Jest 在这里使用了第三方库 yargs execachalk 等来解析执行并打印命令。

模拟

在复杂的测试场景,我们一定绕不开一个 Jest 术语:模拟(mock)

在 Jest 文档中,我们可以找到 Jest 对模拟有以下描述:”模拟函数通过抹去函数的实际实现、捕获对函数的调用,以及在这些调用中传递的参数,使测试代码之间的链接变得容易“

简而言之,可以通过将以下代码片段分配给函数或依赖项来创建模拟:

jest.mock("fs", {
   
  readFile: jest.fn(() => "wscats"),
});

这是一个简单模拟的示例,模拟了 fs 模块 readFile 函数在测试特定业务逻辑的返回值。

怎么模拟一个函数

接下来我们就要研究一下如何实现,首先是 jest.mock,它第一个参数接受的是模块名或者模块路径,第二个参数是该模块对外暴露方法的具体实现

const jest = {
   
  mock(mockPath, mockExports = {
    }) {
   
    const path = require.resolve(mockPath, {
    paths: ["."] });
    require.cache[path] = {
   
      id: path,
      filename: path
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值