概念
在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试 [来源请求] ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。能进行单元测试的函数/组件,一定是低耦合的,这也从一定程度上保证了我们的代码质量。通过 given-when-then 的结构,可以让你写出比较清晰的测试结构,既易于阅读,也易于编写。
为什么要做单元测试
单元测试可以让开发工作更加高效,单元测试适合的场景是敏捷开发。敏捷迭代开发最重要的就是要快,快速迭代、持续交付用户价值,而没有单元测试很难快起来。因为每次开发、发布都需要投入人力测试,并且随着项目迭代和复杂度会上升,也需要我们定期重构,而没有单元测试也不敢轻易重构,这样下去代码会变成“屎山”,代码越来越烂,写的就越来越慢,就会出现更多的 bug,而为了快速修复 bug 又不敢重构,进一步导致代码变烂,形成恶性循环。在这样的场景中,单元测试就是很有必要的。
Jest
Jest 是一款由 Facebook 提供的 JS 测试框架,支持 Babel、TS、Node、React、Angular、Vue 等诸多前端框架。
测试的执行方法
test('test unit name', () => {
// 执行测试代码
});
// or
it('test unit name', () => {
// 执行测试代码
});
执行断言的方法
test('test unit name', () => {
expect()
// expect.匹配器
})
常用匹配器
toBe // 值匹配
toEqual // 对象、数组的深度匹配
toStrictEqual // 更严格的匹配
not // 不匹配,后边可以跟其他匹配符
toBeTruthy // true 匹配
toBeFalsy // false 匹配
toBeNull // null 匹配
toBeNaN // NaN 匹配
toBeCloseTo // 浮点数运算匹配
toHaveBeenCalled // 是否执行回调匹配
toThrow // 异常匹配
测试函数
export const sum = (a, b) => a + b;
test("1+2=3", () => {
expect(sum(1, 2)).toBe(3);
})
测试回调
const map = (arr, callback) => arr.map(callback);
test("map [1,2,3] 回调函数执行3次", () => {
const mockFn = jest.fn((x) => x * 2);
map([1, 2, 3], mockFn);
// 获取执行次数
expect(mockFn.mock.calls.length).toBe(3);
});
test("map [1,2,3] 回调函数返回 2,4,6", () => {
const mockFn = jest.fn((x) => x * 2);
map([1, 2, 3], mockFn);
expect(mockFn.mock.results[0].value).toBe(2);
expect(mockFn.mock.results[1].value).toBe(4);
expect(mockFn.mock.results[2].value).toBe(6);
});
测试异步
const fetchData = () =>
new Promise((resolve) => {
setTimeout(() => {
resolve("result");
}, 300);
});
test("返回值为 result", async () => {
const data = await fetchData();
expect(data).toBe("result");
});
模块间的依赖
绝大部分项目中都有模块的依赖,Jest 中可以使用 Fake/Stub/Mock/Spy 等方式去处理模块间的依赖关系。
// 替代整个模块, 可以用mock的数据代替模块返回的内容,从而保证我们的期望
import player from "./player";
const mockPlayFile = jest.fn();
jest.mock("./player", () => {
return jest.fn().mockImplementation(() => {
return { playFile: mockPlayFile };
});
});
查看测试覆盖率
jest --coverage
在项目中集成 Jest
1. 创建项目 & 安装依赖
## 使用 vite 模板创建 react-ts 项目
yarn create vite react-jest --template react-ts
## 安装 jest
yarn add jest @types/jest -D
## 使用 babel 转译
yarn add babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript -D
## 或者使用 ts-jest
yarn add ts-jest ts-node -D
## 安装测试 UI、Event 等内容的工具
yarn add react-test-renderer @testing-library/jest-dom @testing-library/react @testing-library/user-event -D
## mock 以及 eslint 工具
yarn add identity-obj-proxy -D
yarn add eslint-plugin-jest -D
## 解决 environment 报错
yarn add jest-environment-jsdom -D
2. 创建测试目录
├── __test__
│ ├── __mock__
| | └── fileMock.js
│ └── App.test.tsx
└── src
3. 配置文件
// package.json 添加脚本命令
{
"scripts": {
"test": "jest"
}
}
// jest.config.ts 配置 jest
import type { JestConfigWithTsJest } from "ts-jest";
const jestConfig: JestConfigWithTsJest = {
// 环境
testEnvironment: "jsdom",
// 是否生成测试覆盖率
collectCoverage: true,
// 匹配的文件
transform: {
"^.+\\.tsx?$": "ts-jest",
},
// 模块映射
moduleNameMapper: {
"\\.(gif|ttf|eot|svg|png)$": "<rootDir>/__test__/__mocks__/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
},
};
export default jestConfig;
// fileMock.js 添加资源文件的 mock
module.exports = "test-file-stub";
4. 运行测试
举个简单的例子:
// App.test.tsx 用到了 testing-library 测试库
import { render, screen } from "@testing-library/react";
import user from "@testing-library/user-event";
import App from "../src/App";
test("正确显示button的内容", async () => {
// 渲染
render(<App />);
// 获取页面元素
const buttonCount = await screen.findByRole("button");
expect(buttonCount.innerHTML).toBe("count is 0");
// 模拟用户点击行为
await user.click(buttonCount);
expect(buttonCount.innerHTML).toBe("count is 1");
});
yarn test
$ jest
PASS __test__/App.test.tsx
✓ 正确显示button的内容 (112 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
App.tsx | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.532 s, estimated 4 s
Ran all test suites.
✨ Done in 5.80s.
Mocha
Mocha 是比较灵活成熟的 JavaScript 单元测试框架,由于没有内部集成,所以可以自主选择断言库和侦听库。另外 Mocha 独特的功能是它不止可以在 Node.js 里运行测试,还可以在浏览器里运行测试。
Jasmine
Jasmine 是 Jest 的底层库,主攻 BDD(Behavior-Driven development,即行为驱动开发) 断言库与异步测试的自动化测试框架,没有外部依赖。运行在 Node.js 上,没有外部库,所以可以兼容所有的框架和库,更加灵活,配置过程也更加繁琐。
其他
除了以上的几种还有 AVA、Tape 等单元测试库,Testing Library 等组件测试库以及其他测试工具。