快速上手前端中的单元测试(一)

两种开发模式的对比

下面是两种开发模式的示例图,其中我们可以清晰的看到瀑布流开发模式风险高、价值小。而敏捷迭代的开发模式风险小、价值大。

开发模式

为什么要单元测试

在传统的瀑布流开发模式下,项目流程是这样的——产品经理提出需求,设计师根据需求制定设计,前后端工程师负责开发,最后由测试工程师进行测试。如果测试发现了 BUG,或者产品认为项目某个功能不符合预期,开发团队就需要不断修改和加班。这种模式下,各个环节之间缺乏联系,效率低下,产品交付时间难以预估。这样的话客户就会不满意。

因此采用如果我们想采用敏捷迭代的开发模式,单元测试是不可或缺的。单元测试能提高代码质量和测试覆盖率,降低需求不匹配的风险。

领略前端测试流程

虽然在前端开发中我们已经有了 TypeScript、ESLint 等工具,但这还远远不够。我们可以使用前端自动化测试工具,如单元测试、端到端测试、集成测试等等。接下来我们用一个代码片段来领略单元测试。

我们有一个 sum 函数,它实现了一个简单的加法运算。

1
2
// sum.js
const sum = (num1, num2) => num1 + num2;

然后我们想对它进行测试,看它是否符合我们的预期。我定义一个 expect 方法,它返回一个 toBe 的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// sum.test.js
// expect 函数接受一个返回值,tobe 方法接受一个预期值。当条件不符合预期值就会抛出错误。
const expect = result => {
return {
toBe: actual => {
if (result !== actual) {
throw new Error(
`预期值与结果不匹配 预期值为 ${actual},返回值为${result}`
);
}
},
};
};

// 传递 2 + 3 , 预期值为 5
expect(sum(2, 3)).tobe(5);

实现一个简单测试函数

当预期值与返回值相等的时候,不会抛出任何错误。但当预期值与返回值不同时

给它加上一个新功能

expect 函数会抛出错误,但这引出了另一个问题:错误信息是固定的。我们希望能传递具体的错误信息,以便更准确地定位出现问题的函数。

我们需要定义一个 test 函数 ,它接受两个值,一个是错误信息,另一个为回调函数。

1
2
3
4
5
6
7
8
9

const test = (desc, fn) => {
try {
fn();
console.log(`测试通过 ${desc}`);
} catch (error) {
console.log(`测试未通过 ${desc},${error}`);
}
};

可以看到当预期值与返回值相等值,它会返回我们想要的提示信息。当预期值与返回值不相等的时候它就会返回测试未通过并返回错误信息。
这就是一个简单的单元测试案例。

前端自动化框架 Jest

要知道我们一个项目,要测试的东西有很多,只有这两个方法是远远不够的,所以我要用到前端单元测试框架。

在前端领域中,最热门的单元测试框架莫过于 Jest。它是由 Facebook 公司维护的一个开源项目。正如其官网所介绍,Jest 具有众多优点。

下面我们用 jest 重构我们的之前写的代码片段。

1
2
3
4
5
6
// sum.js
const sum = (num1, num2) => {
return num1 + num2;
};

module.exports = sum;
1
2
3
4
5
6
// sum.test.js
const sum = require("./sum");

test("1+2 是否等于 3", () => {
expect(sum(1, 2)).toBe(3);
});

可以看到,在框架的帮助下,代码量显著减少了。jest 主要帮我们做两件事:

  • 单元测试:可以理解为模块测试
  • 集成测试:多模块测试

学习使用 jest 配置

虽然我们可以直接使用 Jest 进行自动化测试而无需配置,但学习 Jest 的配置选项能让我们根据特定需求定制测试环境,甚至利用 Jest 提供的其他功能。

生成 jest 配置

首先我们要运行指令,生成 jest 的配置文件。

1
npx jest --init

接着它会问你几个问题,根据需求填写即可。

  • Would you like to use Typescript for the configuration file? 您想在配置文件中使用 Typescript 吗?如果你开发的是 TS 项目就选是,不是填否即可。
  • Choose the test environment that will be used for testing 选择将用于测试的测试环境? 如果你的代码是跑在服务端的就选择 Node ,不是则选着第二个。
  • Do you want Jest to add coverage reports? 你想让 Jest 添加覆盖率报告吗?默认选择是。
  • Which provider should be used to instrument code for coverage? 应该使用哪个提供程序来对代码进行覆盖率检测?我认为如果是 Node 服务端的话就选 V8,如果是一个前端项目想必都会配置 babel ,那么就选择 babel 即可,这是我认为的。
  • Automatically clear mock calls, instances, contexts and results before every test? 在每个测试之前自动清除模拟调用、实例、上下文和结果?默认选择是。

生成测试覆盖率

运行指令

1
npx jest --coverage

此时一切正常的话,它会生成在命令行终端中输出一个简易的表格显示它的测试覆盖率和其他信息。同时在会在项目的文件中生成 coverage 的文件。在里面可以找到一个 index.html 文件。在浏览器运行你会看到更详细的测试信息。

但是刚才我们选着了要运行在浏览器上,从 Jest 28 开始,jest-environment-jsdom 不再默认包含在 Jest 中,因此你需要单独安装它。

安装

1
npm install --save-dev jest-environment-jsdom

更新配置

1
2
3
4
5
6
// jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom',
// 其他配置项...
};

如果配置的是 jsdom 别急着该他是jest-environment-jsdom 的别名。其实这也是我问 ChatGPT 得出来的答案。我从很早就接触到前端了。只不过那个时候我稍微遇到点报错就没有耐心去学下去了。那个时候懵懂无知,因为那个时候根本没有 AI 这个东西,上百度搜,很难搜的到。因为我看不懂报错的信息,整段信息都复制上搜索能找到的概率是很低的。所以 AI 真牛、ChatGPT 真牛。不然以我这种水平是不会接触这些东西的哈哈。

结果

测试覆盖缩略图

测速覆盖详细图

使用 Babel 对代码转换

我们知道在 Nodejs 的环境下,默认是以 commonJS 为模块化的。如果我们写了 ESModule 的语句 Node 会立即报错。所以我们要使用 Babel 对代码进行转换,在 Node 环境下使用 commonjs 规范,在浏览器环境下使用 ESModule

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

配置 Babel

1
2
3
4
// babel.config.js
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
};

对 jest 配置就落下帷幕了,在 jest 官方文档中还有很多 jest 的配置。我就不写了,官方文档也写的清清楚楚。主要是我现在用不到这些东西。如配置 TypeScriptESLint 等。我可不想在学习的过程中受到 TS 的类型问题,也不喜欢 ESLint 对我的代码检测。出问题了运行的时候 Node 会报错。

总结

我们通过对两种开发方式的介绍,经过比较我们得出敏捷迭代的方式是最好的开发方式之一。紧接着手动实现了一个简单的前端测试的流程。但是我们发现如果手动一个个测试很麻烦。所以我们又使用到了前端自动化测试工具 jest ,jest 最主要的功能就是单元测试和集成测试。然后我们写了 jest 配置相关的知识。