Extra07 - Jest - 单元测试框架

此篇为番外,为选入本篇的原因为 Jest 的功能与单元测试的方式多元且复杂,此篇仅能做初步的介绍,因此放於番外作补充。

随着 TDD 的流行,单元测试在现在的程序开发中扮演着重要的角色,因此每个专案中都需要有个测试框架供开发者撰写测试。

在 Jest 之前, JavaScript 要做测试时会需要合并多个工具(例如: Karma (执行器)+ Chai (断言( assertion ))+ Mocha (测试框架)+ Sinon ( Mock 工具) + Istanbul (测试覆盖率)),需要花时间在各工具间的整合上。

完善的测试框架 - Jest

Jest 是个 JavaScript 的测试框架,它提供了完整的单元测试环境,只要使用 Jest ,不用安装其他工具,就可以有许多丰富的功能,例如:多样的判断方法、分析测试涵盖率、 mock 功能与良好的错误提示讯息等。

Jest 可以测试的范围涵盖了 JavaScript 相关的技术,包含後端的 Node.js 与前端的 Vue 、 Angular 或 React ,是个完善的测试框架,因此只要使用 Jest 一套测试框架,就可以应付一个专案所需的测试需求,大幅减少配置时间。

使用 Jest

在使用 Jest 前需要先使用 npm 安装:

npm install jest --save-dev

假设要测试的程序码 sum.js 如下:

function sum(a, b) {
  return a + b;
}
module.exports = sum;

我们撰写的测试 sum.test.js 如下:

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

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

执行 Jest :

npx jest

Jest 预设会找出专案中与 .test 相符的档案,并将其视为测试脚本执行,结果如下:

 PASS  ./sum.test.js
  ✓ adds 1 + 2 to equal 3 (1 ms)

Jest 会显示出执行的测试名称以及结果。

Jest 的配置

Jest 所提供的预设配置,可以让使用者不用自己设定就可以直接使用( Zero Config )。

如果想要自己配置的话,可以在配置档 jest.config.js 中作配置:

module.exports = {
  testMatch: ['**/(*.)unit.js'],
};

上面的例子会将 Jest 找寻测试档名的方式改为与 unit.js 相符的档案。

Jest 的配置项可以在官网中找到详细的说明

Jest 的指令

jest 指令是作为执行测试的方式:

# Run tests
npx jest

它有许多的选项可以让使用者配置,例如下列的例子:

# Run tests related with minus.js
npx jest --findRelatedTests minus.js

# Watch files for changes and rerun tests related to changed files
npx jest --watch

# Output coverage
npx jest --coverage

全部的选项可以在官网中找到详细的说明

测试的架构

Jest 的测试是使用全域方法 it (也可以使用 test )来撰写:

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

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

除了 it 外, Jest 还提供了其他的全域方法让开发者使用,像是 afterAllbeforeEachdescribe 等。

如果不想要隐性相依全域方法,可以使用 import {describe, expect, test} from '@jest/globals' 来导入。

beforeAllafterAll

beforeAllafterAll 会在所有测试开始前与结束前叫用,可以在此设定测试时所需的资源(例如测试资料)。

beforeEachafterEach

除了 All 外, Jest 还提供了 Each 的方法, beforeEach 会在每个测试开始前叫用,而 afterEach 则会在结束前叫用。

describe

describe 方法可以将多个测试视为同一个群组,让 afterbefore 的触发会限制在群组的内部。

以下面的例子为例:

const { sum, minus } = require('./math');

beforeEach(() => {
  console.log('beforeEach');
});

afterEach(() => {
  console.log('afterEach');
});

describe('sum', () => {
  beforeEach(() => {
    console.log('sum beforeEach');
  });

  afterEach(() => {
    console.log('sum afterEach');
  });

  it('adds 1 + 2 to equal 3', () => {
    console.log('sum test');
    expect(sum(1, 2)).toBe(3);
  });
});

describe('minus', () => {
  beforeEach(() => {
    console.log('minus beforeEach');
  });

  afterEach(() => {
    console.log('minus afterEach');
  });

  it('minus 2 - 1 to equal 1', () => {
    console.log('minus test');
    expect(minus(2, 1)).toBe(1);
  });
});

describe 外侧与内侧个别设置了 afterEachbeforeEachdescribe 内侧的只会在内部的测试叫用时触发,外部的在不同的 describe 中都会触发。

测试的断言

测试的断言是判断一个测试是否正确的依据, Jest 提供了许多断言的方式。

以下面的测试为例:

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
  • expect(sum(1, 2)) :将 sum(1, 2) 作为要判断的值
  • `toBe(3) :值应该要是 3

所以当 sum(1, 2) 等於 3 的时候,测试通过,反之则失败。

各个判断方法可以在官网中找到详细的说明

Mock

当测试的对象有相依於其他套件时,我们的测试会因为其他套件而产生不确定性,如果其他套件更新,我们的测试可能不会通过,但这并不是我们测试对象的问题,因此应该将这个变因排除。

Jest 提供多样的 mock 方式供使用者使用,下面举几个例子来说明。

我们有两个模组 join.jscombineString.jsjoin.js 内容如下:

const _ = require('lodash');

function join(array, separator) {
  return _.join(array, separator);
}

module.exports = join;

它引入了第三方库 lodash ,并使用它的 _.join 方法实作本身的功能。

接着是 combineString.js

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

function combineString(array) {
  return join(array, ' ');
}

module.exports = combineString;

combineString.js 使用 join.js 提供的 join 方法来合并字串。

写完功能,我们来做测试,为了避免外部套件影响测试结果,必须要:

  • Mock join.js 中的 lodash
  • Mock combineString.js 中的 join

join.test.js 内容如下:

const { it } = require('@jest/globals');
const _ = require('lodash');
const join = require('./join');

jest.mock('lodash');

it('join by ,', () => {
  join(['a', 'b'], ',');
  expect(_.join.mock.calls.length).toBe(1);
  expect(_.join.mock.calls[0][0]).toEqual(['a', 'b']);
  expect(_.join.mock.calls[0][1]).toBe(',');
});

由於 join 的实作是由 lodash 来做的,因此测试时,我们只需要确认 join 的确有叫用 _.join ,并确保传入的参数正确就行,因此采用的 Jest 的自动 mock 功能,在 lodash 的各个物件中插上可供判断的资讯,并用这些资讯判断叫用是否正确。

接着我们还看 combineString.test.js

const { it } = require('@jest/globals');
const join = require('./join');
const combineString = require('./combineString');

jest.mock('./join');

it('combine string', () => {
  join.mockImplementationOnce(() => 'a b');
  const result = combineString(['a', 'b']);
  expect(result).toBe('a b');
});

combineString 使用 join 作为实作的方式,我们在测试时,不需要验证 join 是否正确,只需要知道叫用 join 後会输出期望的结果就行,因此使用 mockImplementationOnce 来定义期望的结果,并判断是否在取得时有相同的结果。

Mock 是门大学问,要如何使用必须依照各种情境与测试目标来决定, Jest 的官网提供了 Mock API 的详细说明,同时也依照各种使用方式提供了对应的 Guides,想要深入了解的可以参考这些资源。

本文重点整理

  • 撰写测试对於开发已经是不可或缺的一部分,它不仅可以确保程序运行的正确性,还可以作为规格给予共同开发者一个参考,因此,为专案选择一个测试框架就是个必要的工作。
  • Jest 作为一个完整的 JavaScript 测试框架,它提供了单元测试会运用到的各式功能,为使用者提供便利又简单的测试环境,以确保开发的品质。

参考资料


<<:  JS 37 - 滚动网页即自动浮现元素

>>:  【Day16】:Counter的硬体实现

Day31 - Windows 提权(2)-AlwaysInstallElevated、Unattended Installs、Bypassing UAC

AlwaysInstallElevated 设定 在 Windows 当中有一种设定可以让非管理权限...

[iT铁人赛Day6]JAVA的运算简写

既然讲到运算符号,也讲完了运算的优先顺序,那就来说说运算的简写吧 一般我们写运算时会写:"...

Day 18: 人工智慧在音乐领域的应用 (AI作曲-基因演算法二)

今天我们开始详细的介绍作曲是如何与基因演算法做结合 首先我们先快速复习一下基因演算法的流程: (1)...

Day 7 - 基本语法2 (型态)

昨日的回家功课小问题 没错,你如果这样写就会直接报错。 原因其实很简单,就是常数无法运算,就像 &q...

Day 18 prototype 配色与精稿绘制

来找设计师一起 side project,前後端 / UIUX 皆可ㄛ。配对单连结: https:...