随着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);
});

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

Jest

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

引入 Jest 的依赖后,我们可以直接使用其内置的 testexpect 函数,从而大大提高测试的效率和准确性。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 这一匹配器,它用于判断值是否相等。除此之外,还有许多其他类型的匹配器。

值相等

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

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

存在 toBeNulltoBeUndefinedtoBeDefined 匹配器来分别判断值是否为 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 的使用。通过这些技术,我们将能够更高效地编写和维护测试用例,尤其是在处理复杂异步逻辑和外部依赖时。

全面掌握 Jest:从零开始的测试指南(上篇)的更多相关文章

  1. 《大话移动APP测试:Android与iOS应用测试指南》

    <大话移动app测试:android与ios应用测试指南> 基本信息 作者: 陈晔 出版社:清华大学出版社 ISBN:9787302368793 上架时间:2014-7-7 出版日期:20 ...

  2. 推荐——Monkey《大话 app 测试——Android、iOS 应用测试指南》

    <大话移动——Android与iOS应用测试指南> 京东可以预购啦!http://item.jd.com/11495028.html 当当网:http://product.dangdang ...

  3. OWASP固件安全性测试指南

    OWASP固件安全性测试指南 固件安全评估,英文名称 firmware security testing methodology 简称 FSTM.该指导方法主要是为了安全研究人员.软件开发人员.顾问. ...

  4. Web安全测试指南--文件系统

    上传: 编号 Web_FileSys_01 用例名称 上传功能测试 用例描述 测试上传功能是否对上传的文件类型做限制. 严重级别 高 前置条件 1.  目标web应用可访问,业务正常运行. 2.  目 ...

  5. Web安全测试指南--认证

    认证: 5.1.1.敏感数据传输: 编号 Web_Authen_01_01 用例名称 敏感数据传输保密性测试 用例描述 测试敏感数据是否通过加密通道进行传输以防止信息泄漏. 严重级别 高 前置条件 1 ...

  6. 读书笔记——商广明《Nmap渗透测试指南》

    一 Nmap基础学习 1.简介及安装 Nmap是一款由C语言编写的.开源免费的网络发现(Network Discovery)和安全审计(Security Auditing)工具.软件名字Nmap是Ne ...

  7. Mousejack测试指南

    0x00 前言 近日,Bastille的研究团队发现了一种针对蓝牙键盘鼠标的攻击,攻击者可以利用漏洞控制电脑操作,他们将此攻击命名为MouseJack. 攻击者仅需要在亚马逊上以60美元购买设备,改造 ...

  8. 测试指南(适用于Feature/promotion/bug)

    1.提前了解需求,在需求的业务基础和开发的架构基础上分析测试关键点,给出测试策略,甚至需要准备测试数据: 2.分析需求时不要受开发影响,要有自己的分析和判断,包括测试范围,测试时间: 3.在开始测试之 ...

  9. 微信小程序测试指南

    [本文出自天外归云的博客园] 微信小程序本地部署测试方法 下载微信开发者工具 让小程序管理员将测试人员的微信号添加开发者权限 本地设置hosts为测试环境hosts 打开微信web开发者工具并扫码登录 ...

  10. metasploit渗透测试指南概要整理

    一.名词解释 exploit 测试者利用它来攻击一个系统,程序,或服务,以获得开发者意料之外的结果.常见的 有内存溢出,网站程序漏洞利用,配置错误exploit. payload 我们想让被攻击系统执 ...

随机推荐

  1. mysql 删除数据表报错 表删除时 Cannot delete or update a parent row: a foreign key constraint fails 异常处理

    mysql 删除数据表报错 表删除时 Cannot delete or update a parent row: a foreign key constraint fails 异常处理 MySQL报错 ...

  2. IPFS 解决国内 docker mirror 封锁

    IPFS 解决国内 docker mirror 封锁 内容仅用于研究,帮助开发者学习技术知识,以建设祖国 IPFS 技术是当前 Web3 的主要基建设施,提供去中心化存储,以及 libp2p 的去中心 ...

  3. Groovy 基于Groovy实现DES加解密

    groovy 3.0.7 DES加密简介 加密分为对称加密和非对称加密.非对称加密,加解密使用不同的密钥,如RSA:对称加密,加解密使用相同的密钥,如DES(Data Encryption Stand ...

  4. 搭建php环境

    nginx安装在宿主机上 db: 正式采用阿里云rds 测试使用docker安装 注意:报错一般都是需要配置国内镜像源,看之前的配置记录. 除了关闭防火墙,还要设置这个(本地开发环境) 永久关闭 SE ...

  5. 搭建lnmp环境-nginx(第一步)

    建议: 本次lnmp采用yum形式安装,编译安装过于繁琐,操作不好还不如yum安装,所以不推荐. 全部安装在宿主机上,如果需要安装多个版本的软件才使用docker nginx无所谓版本了 刚安装好系统 ...

  6. 错误记录java: JDK isn't specified for module

    跑苍穹外卖的时候遇到了 java: JDK isn't specified for module 'sky-pojo'这一问题 解决办法是通过修改JDK版本,这个项目用的springboot比较早,可 ...

  7. Jmeter函数助手25-log

    log函数用于记录一条日志并返回其值. String to be logged (and returned):函数会返回该值.控制台也能看到该字符 Log level (default INFO) o ...

  8. 【Windows】Win10 20H2版本 管理员身份问题

    问题描述: 从之前的1909版本升级过来的,在一开始就是管理员身份,升级之后还是管理员身份没错 但是打开一些软件又会开始提示是否安全,还有C盘访问权限警告. 解决办法: 参考方案地址 http://w ...

  9. 【Layui】10 颜色选择器 ColorPicker

    文档地址: https://www.layui.com/demo/colorpicker.html 常规选择器: <fieldset class="layui-elem-field l ...

  10. lanczos算法——求解线性方程组时的辅助算法

    lanczos算法 Lanczos算法是一种将对称矩阵通过正交相似变换变成对称三对角矩阵的算法,以20世纪匈牙利数学家Cornelius Lanczos命名. 注意:Lanczos算法只能对" ...