🧪 前端实验室:偏向于前端研究领域和方案总结,🍚 前端大食堂:专注于前端横向对比和评测,📔 前端小笔记 :对前端基础知识和经验的小总结
为什么需要测试
在不断迭代的开发过程中,如何保障我们的核心功能不受影响,回归校验能顺利通过呢?
答案可能是 测试,很多测试,尽可能面面俱到的测试。
封面图放了一个典型的 测试金字塔 的图,从底至上分别是 单元测试、视觉测试、集成测试。
所以,我们该从哪里开始 🤔 ?我们先仔细看看各类测试的区别:
单元测试 / Unit Testing
- 易于编写且易于运行。
- 保证程序输入输出是符合预期的。
- 如果你几乎没有时间编写测试,那么它们应该是你的首选。
视觉测试 / UI Testing
- 即使你有 100% 的单元测试覆盖率,这并不意味着组件在设备和浏览器上看起来很好,比如某个元素的宽高发生了细微的变化,又或者按钮的响应逻辑产生了变化。
- 视觉测试的目的在于还原真实的浏览器环境,让代码的视觉呈现变得可控。
集成测试 / Integration Tests
- 即使两个组件有单元测试和可视化测试,也不能保证它们能一起工作。
- 在更大的范围,集成测试确保一个系统的各个单元能紧密合作,按照预期完成任务。
- e2eTest 端到端测试也可以纳入集成测试的范围。
一目了然,对于我这种完全没有接触过测试框架,又想开始写一些测试的菜鸟而言,完全可以无负担地,先从接入单元测试开始。
这里我们选择Jest作为我们的单元测试框架,附上当前流行的测试框架的 npm trend :
Jest 是什么?
Jest 是一款优雅、简洁的 JavaScript 测试框架。
Jest 支持 Babel、TypeScript、Node、React、Angular、Vue 等诸多框架!
\
由于 Jest 和 React一样,其开源组织都是 Facebook 公司,所以如果工程项目基于React开发,那么 Jest 几乎就是最好的配套测试框架。
如果你的工程不支持ts,那么按照 Jest的官方起步文档 来安装jest即可。
如果需要支持ts,且需要ts的类型校验,这里推荐安装 ts-jest,除了安装和创建config时有所不同之外,使用方法并不会与 jest 有什么差异。
using npm | using yarn | |
---|---|---|
Prerequisites | npm i -D jest typescript | yarn add --dev jest typescript |
Installing | npm i -D ts-jest @types/jest | yarn add --dev ts-jest @types/jest |
Creating config | npx ts-jest config:init | yarn ts-jest config:init |
Running tests | npm t or npx jest | yarn test or yarn jest |
测试需要做什么?(wip)
对于一个完全只有一丢丢测试经验的前端同学而言,刚起步一般关心以下三个能力:
- 断言(核心功能,校验输入输出)
- 异步(针对请求或者其他异步场景)
- 代码覆盖率(covrage,衡量测试工程的完备性)
以上功能Jest都提供了内置的功能支持,更复杂的场景可以自行探索。
1、断言
常规代码
// 判断一个值是否为True
export const isTrue = (target: any) => {
return Boolean(target) && target !== "false";
};
测试代码
describe("isTrue", () => {
test("case1", () => {
expect(isTrue("true")).toEqual(true);
});
test("case2", () => {
expect(isTrue("false")).toEqual(false);
});
});
2、异步
异步请求代码
export const requestMock = () => {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
resolve({ statu: "200" });
}, 1000);
} catch (err) {
reject(err);
}
});
};
测试代码
可以在代码中使用 await
和 async
。也可以在测试返回的Promise里获取data,注意这里不要漏了 return。
describe("Request", () => {
test("async & await", async () => {
await expect(requestMock()).resolves.toEqual({ statu: "200" });
});
test("promise", () => {
return requestMock().then((data) => {
expect(data).toEqual({ statu: "200" });
});
});
});
3、覆盖率
写完基础的测例之后,我们如果想要了解单测的覆盖率,该怎么做呢?
命令行输入 npx jest --coverage
并回车执行,jest 会基于config来寻找项目中的测例并全量执行,最后会输出这样的一份覆盖率报告:
查看测试结果
Jest 会在控制台输出每一个测例的名称、校验结果以及耗时。
哪些场景下适合写单测?
当业务需求不紧急的时候,完善单测可以保障代码质量,大家也都愿意做。
当业务需求特别紧急的时候,写单测的时间成本就是不得不考虑的问题。
那么哪些场景下,即使业务需求紧急,也可以考虑写单测呢?
- 业务核心模块
- 数据链路比较复杂的场景
这两类的需求,往往调试和返工的成本非常高。
如果我们事先通过单测和类型推断,解决80%以上的隐藏问题,开发的体验就会非常丝滑,单测的时间成本问题也就迎刃而解。
Mocking & Transform
CSSMocking
npm install -D identity-obj-proxy
// package.json (for CSS Modules)
{
"jest": {
"moduleNameMapper": {
"\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\.(css|less)$": "identity-obj-proxy"
}
}
}
在样式对象上,你的所有 className 都会原样返回 (如 styles.foobar === 'foobar'
)
SVGMocking
在根目录下创建一个 svgTransform.js
文件
module.exports = {
process() {
return "module.exports = {};";
},
getCacheKey() {
// The output is always the same.
return "svgTransform";
},
};
在jest.config.js
中配置 svg 的 transform 规则
module.exports = {
...
transform: {
"^.+\.(ts|js|html)$": "ts-jest",
"^.+\.svg$": "<rootDir>/svgTransform.js",
},
};
JsDOM
安装 test-library/jest-dom,该库提供了一组可用于扩展jest的自定义jest匹配器。
npm install --save-dev @testing-library/jest-dom
// In your own jest-setup.js (or any other name)
import '@testing-library/jest-dom'
// In jest.config.js add (if you haven't already)
setupFilesAfterEnv: ['<rootDir>/jest-setup.js']
接下来就可以开始写测试用例了👏
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
Awesom Jest Resources
这里只举了两个,其他可见 awesome-jest
1. Jest-electron
《jest + electron 基础实践——jest-electron》
将 jest 的单测代码放到 electron(底层是 chrome)中运行,在浏览器中运行node代码,获得较为完善的UI测试能力。
具体痛点举例:canvas 渲染UI测试、交互动画、multi-render
2. Jest-puppeteer
让puppeteer被jest所驱动
可以完成的操作:
- e2e,在无头浏览器上进行UI测试
- 借用puppeteer的能力,完成浏览器操作模拟(如页面跳转、单击按钮、填写表格)、断言页面包含的文本、页面结构测试、创建页面截图等。
一些常见问题的探索 🤔
Jest 的冷启动时间有点长怎么办
--watch
/--watchAll
启动 jest 的 watch mode,二次启动的速度会比较快,这也算是曲线解决问题的一种方法吧--runInBand
(别名-i
)
- 在当前进程中连续运行所有测试,而不是创建运行测试的子进程的工作池
- 该功能减少的是创建进程池的时间,当调试几个单测的时候比较有用,但是如果测试的数量比较大,效率就不如开启多进程的策略
Monorepo工程的Jest配置策略
- 可以在root下配置一份通用的
jest.config.js
,包含各类 jest 的配置值 - 如果每一个 package/project 需要自定义的 jest config,Jest Config 提供了
projects
配置项,在root下的jest.config.js
里配置后,Jest 会在所有定义的 project 里尝试运行测例,详见 Jest - project 配置项说明
module.exports = {
projects: [
'<rootDir>/packages/projectA/jest.config.js',
'<rootDir>/packages/projectB/jest.config.js'
],
};
搭配食用的VSCODE插件 🛠
Jest Run It
一个 VS Code 扩展,可帮助您从编辑器运行和调试 Jest 测试。您不再需要为您更改的那个测试运行整个测试套件🎉
- 提供测试用例列表面板
- 可以在每个测试代码文件里,单独运行和调试测例
- 文件级运行调试的Action图标
如果你不想在浏览器里调试代码,希望在编辑器就能得到完整的调试体验,用它吧!
遇到的一些小问题
TypeError: Jest: a transform must export a process
function.
- 检查
jest
和ts-jest
的版本是否一致 - 清除安装包,重新安装最新版本