初探前端单元测试

1,108 阅读5分钟

为什么要写单元测试?

项目开发流程中,开发工作会在需求评审之后进行。

现在前后端分工的情况下,大家会各写各的,到最后再一起进行联调,虽然都有要求要自测通过,但是到最后总会发现有功能遗漏、或者有明显 bug 无法开始联调等等情况。

同样的,在我们提交给测试人员进行 SIT 测试前也需要开发人员自测通过,但大部分团队都是手工操作一波,如果没问题就提交测试了。

流程.png

自我约束终究对人的要求极高,项目实践中,进度常常得不到保障。于是出现了 TDD,测试推动开发,在流程中添加自动化测试流程:

测试流程.png

于是就有了单元测试。在我们动手开发前,添加编写测试用例流程,开发过程中/结束后添加单元测试流程。

也许前些年推行单元测试还很难。但是现在前端的组件化、模块化思想已经深入人心了。而且目前测试工具生态也十分完善。在此基础上,添加单元测试就容易多了。

如何开始前端的单元测试

框架选择

现有流行的测试框架有 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.tsjest.config.js 的语法是不一样的。

  • jest.config.ts 支持 es 的写法,使用importexport作导入导出。
  • jest.config.js 需要使用 commonjs 的规范,需要使用 requiremodule.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 的测试工具版本也是不一样的:

mount 与 shallowMount 的区别

  • mount 会将子组件也渲染出来,比较耗时间
  • shallowMount 不会渲染该组件里潜逃的组件,执行速度更快

如果不是非必要,更推荐使用 shallowMount,而子组件的功能更应该由子组件自身去做单元测试。

总结

Jest 等测试工具生态完善,让单元测试变得简单可执行!如果有可能,也把单元测试加入到你的项目中来吧!

单元测试的优缺点

单元测试缺点很明显,因为工作流程步骤的增加,我们的工作量肯定是增加的。它是比较消耗时间的。

单元测试的优点:

  1. 将思考过程记录下来转化成了可执行的测试用例。磨刀不误砍柴工,后续开发的思路也会更清晰。
  2. 大量减少手动操作,来回自测的工作量。
  3. 大量减少功能遗漏、bug 频发的情况。
  4. 对后期重构组件效率和质量有质的飞跃:如果后期需要重构,组件只需要完成单元测试,基本上能保证其顺利工作。

综上所述,如果你的项目需要持续迭代,更推荐你在流程中添加单元测试。

单元测试不适用于哪些场景

  1. 不适合未做组件拆分的复杂场景
  2. 不适合多页面之间联动的场景

参考