使用 Jest 进行 JavaScript 测试驱动开发 (TDD) 的基础知识

2025-05-26

使用 Jest 进行 JavaScript 测试驱动开发 (TDD) 的基础知识

[JS#4 WIL 🤔 帖子]

测试驱动开发 (TDD) 的核心思想是在编写待测试代码之前,通过编写自动化测试来开始代码开发。JavaScript 中有很多测试运行系统: JasmineJestTapeMocha等等。它们各有特色,但语法非常相似。选择框架应该不成问题,因为

编写测试与语法无关,而更多地与 TDD 哲学有关,

所以我尝试用 Jest 来内化这些概念。我做这个练习的主要目标是了解测试的意义和目的。

在深入探讨之前,以下是我从这场精彩的演讲《测试的魔力》中摘取的一些笔记。

  1. 为什么大多数开发人员讨厌测试?因为它们缓慢、脆弱,而且耗时。
  2. 删除一些测试是完全有效的。
  3. 单元测试目标:它们必须彻底(我们希望它们能够从逻辑上、完整地证明被测单个对象的行为正确)并且稳定(我们不希望每次实现细节发生变化时都破坏测试😟),快速少量(为最简约的表达式编写测试[mmmmmm🤔])。
  4. 不要测试私有方法。但如果在开发过程中可以节省成本,可以打破这条规则。
  5. 模拟是测试替身,它扮演真实应用中某个对象的角色。确保测试替身与 API 保持同步。
  6. 相信合作者会做正确的事。坚持简单。
  7. 提高测试水平需要时间和练习。

被测对象有三个消息来源:

📌传入- 从外部发送给对象的消息
📌自身- 被测对象发送给自身的消息
📌传出- 对象发送给外部的消息。

消息有两种类型:查询 (query)命令 (command)。查询会返回某些内容或不做任何更改。命令类型不返回任何内容但会进行某些更改。

📌 测试规则网格

下面的测试结果网格显示了如何对每种类型的消息进行单元测试。

消息类型 询问 命令
传入 断言结果:
通过断言返回的内容来测试传入的查询消息。
测试接口,而不是实现。
通过对直接公开副作用进行断言来测试传入的命令消息。
务必做到“DRY it out”(严格遵守)。传入消息的接收者对断言直接公开副作用的结果负有全部责任。
发送给自己 忽略:不测试私有方法。 忽略:不测试私有方法。
传出 忽略。传入查询的接收者仅负责涉及状态的断言。
如果消息没有可见的副作用,则发送者不应对其进行测试。
期望使用模拟发送传出命令消息

📌 TDD 的优势

  1. 减少添加新功能或修改现有功能时可能引入的错误
  2. 构建安全网,防止其他程序员的更改影响代码的特定部分
  3. 通过确保代码在新的更改后仍能正常工作来降低更改成本
  4. 减少测试人员和开发人员手动(猴子)检查的需要
  5. 提高对代码的信心
  6. 减少重构过程中对重大变更的担忧

📌 Jest 入门

Jest 是一个 JavaScript 测试框架,注重简洁性,但仍然确保 JavaScript 代码库的正确性。它以快速、安全、可靠地并行运行测试以及独特的全局状态而闻名。为了加快速度,Jest 会首先运行先前失败的测试,然后根据测试文件运行的时间重新组织运行。

此外,Jest 文档完善,配置要求低。它确实让 JavaScript 测试变得轻松愉快。您可以使用yarn或进行安装npm

📌 TDD 的三种模式

  1. 明显的实现。由于您知道如何实现要测试的方法,因此您可以使用实现来编写测试。
  2. 假装成功。如果你知道问题和解决方案,但你无法立即理解如何编写代码,那么你可以使用一个叫做“假装成功”的技巧。
  3. 三角测量。这是进行 TDD 最保守的方法。如果你甚至不知道解决方案,你就会不惜一切代价地走向绿色,红绿循环。

📌 使用 Jest Matchers

常见匹配器

测试值的最简单方法是完全相等。

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
Enter fullscreen mode Exit fullscreen mode

上面的代码片段返回一个“期望”对象。该toBe(3)部分是匹配器。Jest 运行时,它会跟踪所有失败的匹配器,以便打印出合适的错误消息。toBe匹配器用于Object.is测试是否相等。

真实性

在单元测试中,可能还需要检查特殊值undefinednull、 。Jestfalse包含一些辅助函数,可以让开发人员明确预期结果。因此,最好使用一个与代码实际功能最匹配的匹配器。

  • toBeNull仅匹配null
  • toBeUndefined仅匹配undefined
  • toBeDefined与……相反toBeUndefined
  • toBeTruthyif匹配语句视为真的任何内容
  • toBeFalsyif匹配语句视为错误的所有内容
数字

Jest 还提供用于比较数字的匹配器,例如,,,toBeGreaterThan对于浮点数,还有像这样的相等匹配器toBeGreaterThanOrEqualtoBeLessThantoBeLessThanOrEqualtoBeCloseTo

字符串

可以使用 来根据正则表达式检查字符串toMatch

数组和可迭代对象

toContain可用于检查是否可以在数组或可迭代对象中找到特定项目。

例外

toThrow可以用来检查某个函数是否抛出了特定的错误。需要注意的是,被检查的函数需要在包装函数中调用,才能使toThrow异常生效。

还有更高级的 Jest 匹配器用于测试异步代码,即回调和承诺。

📌 Jest 测试实践

这是我第一次使用 Jest 编写 JavaScript 单元测试。这对我来说还很新,所以我需要一些练习😄。我尝试对以下一些方法使用显而易见的实现三角测量模式进行测试。这些方法的完整实现及其对应的测试可以在我的Jest 实践 GitHub 仓库中找到

  • capitalize(string)接受一个字符串并返回首字符大写的字符串。

capitalize.test.js

const capitalize = require('../capitalize');

test('should capitalize lowercase string correctly', () => {
  expect(capitalize("capitalize")).toBe("Capitalize");
});

test("should return '' for strings with length 0", () => {
    expect(capitalize("")).toBe("");
});

// other tests here
Enter fullscreen mode Exit fullscreen mode
  • reverseString(string)接受一个字符串并将其反转返回。下面是我为正常场景编写的测试代码片段。

reverse-string-test.js

const reverseString = require('../reverse-string');

test('should reverse normal strings', () => {
  expect(reverseString("reverse")).toBe("esrever");
});

//other tests here
Enter fullscreen mode Exit fullscreen mode
  • calculator包含基本运算的对象:addsubtractdividemultiply下面的测试代码片段显示,如果除数为零,该方法将抛出错误消息。

calculator.test.js

const calculator = require("../calculator");

//other tests here

test("should throw an error if divisor is 0", () => {
    expect(() => calculator.divide(20, 0)).toThrow("cannot divide by 0");
});
Enter fullscreen mode Exit fullscreen mode
  • caesar cipher凯撒密码是一种替换密码,其中文本中的每个字母都会按字母表向下移动一定位数。更多信息请点击此处

练习的这一部分需要记住的一点是,不需要显式测试较小的函数,只需测试公共函数即可。如果较大的函数运行良好,那么辅助方法也一定运行良好。

caesar-cipher.test.js

const caesar = require("../caesar-cipher");

//other tests here

test('wraps', function() {
    expect(caesar('Z', 1)).toEqual('A');
});

test('works with large shift factors', function() {
    expect(caesar('Hello, World!', 75)).toEqual('Ebiil, Tloia!');
});

test('works with large negative shift factors', function() {
    expect(caesar('Hello, World!', -29)).toEqual('Ebiil, Tloia!');
});
Enter fullscreen mode Exit fullscreen mode
  • 数组分析。此函数接受一个数字数组,并返回一个具有以下属性的对象:averageminmaxlength

analyze.test.js

const analyze = require("../analyze");
const object = analyze([1,8,3,4,2,6]);

test("should return correct average", () => {
    expect(object.average).toEqual(4);
});

test("should return correct min", () => {
    expect(object.min).toEqual(1);
});

// other tests here
Enter fullscreen mode Exit fullscreen mode

查看此处包含的代码片段的 github 存储库,获得测试的完整图片。

以上概念和要点是使用 Jest 进行 TDD 的基础知识。还有很多内容需要学习,例如更高级的匹配器、模拟、测试代码的异步部分等等。我还需要学习这些内容,这留到下一篇开发文章再说吧😆。

为持续学习干杯!🍷

[参考]

  1. TDD 的重要性
  2. TOP 测试基础知识
  3. Jest 入门文档
  4. Jest 官方文档
文章来源:https://dev.to/pat_the99/basics-of-javascript-test-driven-development-tdd-with-jest-o3c
PREV
JavaScript 提升
NEXT
React:编写自定义 API 钩子