前端测试介绍及Jest 基本应用
在开发的时候,总是有各种理由不写测试,像是开发时间太赶、写测试太耗时间、写测试好无聊等等,很多人觉得没写测试照样能开发能上线,但真的是这样吗?本篇就来好好介绍前端测试的大小事。
为什么要写测试?
要做什么事情,总是要有个说服人的原因,所以我列了几点写测试的优点:
1. 确保程式码正确,防止无预期的错误:像是改了A 功能坏B 功能。
2. 方便重构程式码:测试能确保重构后的功能依然是正确的。
3. 交接容易:新人进入专案,能快速了解功能,且较不容易改坏原本的功能。
听起来好处很多,那写测试跟一般的人工测试有什么不同呢?
以测试来说总共有分成三个种类:
1. 单元测试( Unit Text ):针对程式中的最小单元进行测试,例如:Function、Class。
2. 整合测试( Integration Test ):针对多个组件或模组之间的互动和整合测试,例如:使用者点击按钮跳出弹跳视窗。
3. 端对端测试( End-to-End Test ):模拟真实使用者敬,测试整个应用程式的功能和流程,例如:使用者登入、使用者注册。
大部分的人工测试都会归类在端对端测试,单元测试及整合测试都会是在开发阶段所进行。
而这三个种类又发展出一个测试金字塔的概念:

越靠近金字塔顶端(E2E 测试),所花费的成本越高,但是越贴近使用者
越靠近金字塔底部(单元测试),所花费的成本越低,但是越贴近开发者
可以看到,进行越多的端对端测试,就会需要花更多的时间以及人力成本,像是写测试报告以及人工测试,但是却是最符合实际使用者的操作流程。所以大部分的专案都会尽量多做端对端测试,以确保使用者体验。
但这也代表,如果今天有程式码写错,可能只是很简单的逻辑错误,但却会导致花更大量的时间在反覆进行端对端测试。所以如果今天能够做好单元测试及整合测试,就能防止花更多的时间在进行E2E 测试。
JavaScript 测试框架
讲了那么多写测试的好处,这就来介绍有哪些前端的JavaScript 测试框架:
1. Jest:由FaceBook 所开发,在react 专案上使用的人较多。
2. Mocha:主要是Node.js 的测试框架。
3. Karma:angular cli 预设搭配Jasmine 作为测试框架。
4. Vitest:近年来兴起的测试框架,同Vite 主打速度快。
这边列了比较常见且较多人使用的测试框架,来做个详细的比较:

| 种类 | Jest | Mocha | Karma | Vitest |
| 发布时间 | 2014 | 2011 | 2012 | 2020 |
| 开发者 | Open source | 谷歌 | Vite | |
| 下载量 | 1 | 2 | 3 | 4 |
| 是否内建断言库 | ✅ | ❌(需搭配chi) | ❌(需搭配Jasmin) | ✅ |
| 是否含模拟函式 | ✅ | ❌(需搭配Sinon.js) | ❌(需搭配Sinon.js) | ✅ |
| 跑测试速度 | 2 | 3 | 4 | 1 |
- 断言:判断数入是否符合预期,Ex: expect(1+1).toBe(2)
- 模拟函式:模拟元件或函式,Ex: jest.fn()
目前比较主流且最多人用的是Jest,Mocha 及Karma 没有内建基本的断言及模拟函式,且测试速度较慢,而Vitest 测试速度最快,但发展还不成熟,可以持续观望。
Jest 特色
内建断言库
内建模拟函式
内建测试覆盖率
内建快照测试
CRA内建,Next.js、Vite也支援
基本语法
假设今天要测试一个加总的函式是否正确:
sum.js
const sum = (a, b) => {
return a + b;
};
export default sum;那测试代码就会长这样:
sum.test.js
import sum from './sum.js';
describe("sum function testing", () => {
it("add 1 + 2 to equal 3", () => {
expect(sum(1,2)).toBe(3);
});
});describe : 大范围的测试描述
it / test : 详细的测试案例描述
expect : 断言
toBe : 断言的方法
在Terminal 输入:
jest sum.test.js就会去跑有测试标记的档案,并显示测试结果:

除了基本语法外,Jest 还提供四个可以让测试代码更整洁的方法
1. beforeAll : 在所有测试开始前执行
2. beforeEach:在每个测试案例开始前执行
3. afterEach:在每个测试案例结束后执行
4. afterAll:在所有测试结束后执行
模拟函式
在测试中,需要把测试项目尽量单一化,所以会尽量把一些外部的引入的函式或模组进行模拟,比较常见的就是模拟发api 回传:
fetchData.js
import axios from 'axios';
import url from './url.js';
const fetchData = async() => {
const response = await axios.get(url);
return 'My content is:' + response.data;
}
export default fetchData当我们要测试fetchData这个函式时,就需要去模拟axios 回传,只测试函式本身的功能,所以测试程式码会是这样:
fetchData.test.js
import fetchData from './fetchData.js'
import axios from 'axios';
jest.mock('axios'); //Mock axios
describe('test fetchData function',() => {
it('call fetchData with "Hello" text get "My content is:Hello", () => {
axios.get.mockResolvedValue({ data: 'Hello' }); // Mock response
const res = fetchData();
expect(res).toEqual('My data is:Hello)
};
});这边需要先使用jest.mock去模拟axios 套件,再使用mockResolvedValue去模拟axios.get函式回传的response,这样就可以很单纯的测试这个函式回传是否正确,而不用实际的发API 请求。
测试覆盖率
Jest 有提供一个测试覆盖率的功能,可以显示有哪些程式码有被测试到,只要下指令
jest --coverage就可以在Terminal 显示测试覆盖率

不过这样看起来很不好看,所以Jest 会同时建立一个coverage 的资料夹,里面会有一个html 档案,打开就会显示转换后的图表

它总共有四个指标
1. Statements : 有多少比例语句被执行到,一个 console.log(); 就算是一个语句,一行中可以有多个Statements。
2. Branches:条件语句,像是if ... else 或是switch,每个情况都是一个Branch。
3. Functions:一个档案有多少比例的函式被执行到。
4. Lines:有几行的程式码被执行到,基本上Lines 的数量会小于Statements 的数量
并不是需要所有指标都100% 被覆盖,最重要的就是Branches跟Functions,属于会影响功能逻辑的程式码,能尽量提高测试覆盖率是最好的。
快照测试Snapshot Testing
快照就如同字面上的意思,就是快速的拍一张照,只是这一张照片是dom 元素的照片,用意是当UI 有改变时,如果有建立快照,Jest 就可以很快的把当下的DOM跟快照做比较,找出有哪里不一样,这样可以很快地抓到错误。
假设现在有一个函式会根据传入的文字回传一个h1 的标题元素
getTitleHtml.js
const getTitleHtml = (text) => {
return `<h1>${text}</h1>`;
}
export default getTitleHtml;Jest 提供toMatchSnapshot()断言方法去比较快照。
getTitleHtml.test.js
import getTitleHtml from './getTitleHtml';
describe('testing snapshot', () => {
it('match h1 element snapshot', () => {
expect(getTitleHtml("Hello")).toMatchSnapshot();
}
} 跑一次测试会得到下面的结果

在第一次跑测试的时候,会在__snapshots__ 档案建立快照

里面的内容会是长这样
__snapshots__/getTitleHtml.test.js.snap
exports['testing snapshot match h1 element snapshot 1'] = "<h1>Hello</h1>"它是利用describe跟it的测试描述去记录DOM 的内容。
那再来测一次原本的函式,这次给他不一样的内容:
import getTitleHtml from './getTitleHtml';
describe('testing snapshot', () => {
it('match h1 element snapshot', () => {
expect(getTitleHtml("Hello World !")).toMatchSnapshot(); // 更改 DOM 內容
}
} 再跑一次测试就会跑出错误,并且显示哪边不一样。

如果想更新快照,只要在watch 模式输入u (update),就会把快照的内容更新成现在的DOM 内容。
总结
1. 在开发时多写单元测试及整合测试是可以很有效的减少E2E 的测试成本及时间。
2. 以主流框架来说,Jest是最多人使用且功能完善的。
3. 大部分的JavaScript 的测试框架语法都大同小异,所以只要学习某一个框架语法都是可以通用的。
4. Jest 提供的测试覆盖率可以有效的找出未测试的程式码。
5.快照测试的用意比较像是提醒开发者UI 有不一样,所以通常会在确认画面内容不会更动的情况下进行测试,不然会导致常常测试不通过。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!