为什么要写单元测试?
项目开发流程中,开发工作会在需求评审之后进行。
现在前后端分工的情况下,大家会各写各的,到最后再一起进行联调,虽然都有要求要自测通过,但是到最后总会发现有功能遗漏、或者有明显 bug 无法开始联调等等情况。
同样的,在我们提交给测试人员进行 SIT 测试前也需要开发人员自测通过,但大部分团队都是手工操作一波,如果没问题就提交测试了。
自我约束终究对人的要求极高,项目实践中,进度常常得不到保障。于是出现了 TDD,测试推动开发,在流程中添加自动化测试流程:
于是就有了单元测试。在我们动手开发前,添加编写测试用例流程,开发过程中/结束后添加单元测试流程。
也许前些年推行单元测试还很难。但是现在前端的组件化、模块化思想已经深入人心了。而且目前测试工具生态也十分完善。在此基础上,添加单元测试就容易多了。
如何开始前端的单元测试
框架选择
现有流行的测试框架有 Jest、Mocha,两者的区别不大。
Jest 有一个特别的地方是:无配置化。不需要任何多余的配置,安装之后就能执行!
下面以 Jest 为例,项目为前端极为普通的一个 nodejs 项目。
安装
在项目中的根目录安装 jest
npm install --save-dev jest
配置
在项目中的根目录的 package.json
{
"script": {
"test": "jest"
}
}
第一个测试用例
假设项目根目录有一个 index.js,里面有一个 formatDate 的日期格式化函数。
在根目录下,新建一个 tests 文件夹,然后新增一个 common.test.js,我们就可以 开始书写测试用例了。
import { formatDate } from 'index';
descibe('formatDate 测试') {
it('formate 0 返回 1970-01-01', () => {
expect(formatDate(0)).toBe('1970-01-01');
});
test('formate -1 返回 1969-12-31', () => {
expect(formatDate(0)).toBe('1970-01-01');
});
...
// 疯狂地在搞事的边缘试探
}
然后执行
npm run test
测试报告就会在控制台显示出来了。
一切就是这么简单!
以上代码中的几个常用语句如下:
- desc 描述要测试的模块,里面可以定义一些公共的方法、变量,方便每个测试用例使用。
- it/test,描述要测试的功能,以及测试代码。
- expect 执行测试代码的地方,获取最后返回的值。
- toBe 用于判断基础类型是否相等的匹配器。
- toEqual 为判断对象类型是否相等的匹配起。
- 其他常用的匹配器见:jestjs.io/docs/using-…
【疑问】it 与 test 的区别
对非英语母语程序员基本没有区别,两者都是描述下面要测试的内容。
- it dosomething return something. 检查行为的返回值
- test something whether is something or not. 检测值是否符合要求
进阶踩坑
配置文件
可以通过新建文件 jest.config.js
配置,可以通过命令行提示进行配置。
npx jest --config
坑点注意
文件jest.config.ts
和 jest.config.js
的语法是不一样的。
jest.config.ts
支持 es 的写法,使用import
、export
作导入导出。jest.config.js
需要使用 commonjs 的规范,需要使用require
与module.exports
作导入导出。
如果碰到 import 报错的问题,可以检查一下文件后缀名。
mock 与异步
jest mock 功能非常强大。可以 mock 函数,可以拦截文件,进行 mock,当然异步请求也不再话下。
// mock 函数
jest.fn(() => {});
// mock 计时器
jest.useFakeTimers();
jest.runAllTimers(); // 执行所有计时器,跳过等待时间
// mock 文件
jest.mock(文件);
// mock axios
import axios from "axios";
jest.mock("axios");
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Andrew" },
];
axios.get.mockResolvedValueOnce(users); // 返回正常的数据
const message = "Network Error";
axios.get.mockRejectedValueOnce(new Error(message)); // 返回异常
Vue 与 Typescript
需要安装 Vue 与 Typescript 的插件。
推荐使用 Vue cli 配置,如果是老项目,也可以复制通过 vue-cli 创建出来的内容。
Vue3 配置如下:
// package.json
{
"devDependencies": {
"@vue/cli-plugin-unit-jest": "~4.5.11",
"@vue/test-utils": "^2.0.0-0",
"vue-jest": "^5.0.0-0"
}
}
// jest.config.js
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
transform: {
'^.+\\.vue$': 'vue-jest'
}
}
Vue3 + Typescript 配置:
{
"devDependencies": {
"@types/jest": "^24.0.19",
"@vue/cli-plugin-unit-jest": "~4.5.11",
"@vue/test-utils": "^2.0.0-0",
"vue-jest": "^5.0.0-0"
}
}
// jest.config.js
module.exports = {
"preset": "@vue/cli-plugin-unit-jest/presets/typescript-and-babel",
"transform": {
"^.+\\.vue$": "vue-jest"
}
}
坑点注意
Vue Test Utils有多个版本,如果项目报错,检查一下工具库是否为 @vue/test-utils
,如果不是升级一下 vue-cli。
另外 vue2 和 vue3 的测试工具版本也是不一样的:
- Vue Test Utils 1 targets Vue 2.
- Vue Test Utils 2 targets Vue 3.
mount 与 shallowMount 的区别
- mount 会将子组件也渲染出来,比较耗时间
- shallowMount 不会渲染该组件里潜逃的组件,执行速度更快
如果不是非必要,更推荐使用 shallowMount,而子组件的功能更应该由子组件自身去做单元测试。
总结
Jest 等测试工具生态完善,让单元测试变得简单可执行!如果有可能,也把单元测试加入到你的项目中来吧!
单元测试的优缺点
单元测试缺点很明显,因为工作流程步骤的增加,我们的工作量肯定是增加的。它是比较消耗时间的。
单元测试的优点:
- 将思考过程记录下来转化成了可执行的测试用例。磨刀不误砍柴工,后续开发的思路也会更清晰。
- 大量减少手动操作,来回自测的工作量。
- 大量减少功能遗漏、bug 频发的情况。
- 对后期重构组件效率和质量有质的飞跃:如果后期需要重构,组件只需要完成单元测试,基本上能保证其顺利工作。
综上所述,如果你的项目需要持续迭代,更推荐你在流程中添加单元测试。
单元测试不适用于哪些场景
- 不适合未做组件拆分的复杂场景
- 不适合多页面之间联动的场景