前端項(xiàng)目中寫單元測(cè)試其實(shí)很簡單
共 13763字,需瀏覽 28分鐘
·
2024-05-24 08:50
大廠技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
本文轉(zhuǎn)載于UCloud云通信技術(shù)團(tuán)隊(duì)
一、關(guān)于自動(dòng)化測(cè)試
1、測(cè)試分類
自動(dòng)化測(cè)試類型常分為以下三種,各有優(yōu)缺點(diǎn):
-
單元測(cè)試(Unit Test)
-
對(duì)項(xiàng)目中低耦合的工具類庫和公共子組件進(jìn)行測(cè)試,較為簡單,能在一定程度上保障代碼質(zhì)量 -
集成測(cè)試(Integration Test)
-
對(duì)于耦合度較高的函數(shù)/組件對(duì)外暴露的接口進(jìn)行測(cè)試,能較大程度保障產(chǎn)品質(zhì)量,但開發(fā)成本高 -
UI測(cè)試(UI Test)
-
前端中UI變動(dòng)大,適合人工檢查
了解測(cè)試術(shù)語
-
1、TDD(測(cè)試驅(qū)動(dòng)開發(fā)) -
2、BDD(行為驅(qū)動(dòng)開發(fā)) -
3、測(cè)試覆蓋率 -
4、快照測(cè)試 -
5、模擬函數(shù) -
6、斷言
2、單元測(cè)試框架
-
Jest[1]:是一個(gè)廣受歡迎的單元測(cè)試框架,簡單易用,功能強(qiáng)大。 -
Vitest[2]:它由 Vue / Vite 團(tuán)隊(duì)成員開發(fā)和維護(hù),在 Vite 的項(xiàng)目集成它會(huì)非常簡單,而且速度非常快。 -
Mocha:一個(gè)靈活的測(cè)試框架,需要各種插件來配合使用。 -
Karma:能在真實(shí)的瀏覽器中測(cè)試,可配置其他單元測(cè)試框架 -
Jasmine:功能全面的測(cè)試框架,相對(duì)復(fù)雜、不夠靈活
測(cè)試框架太多,且各有優(yōu)勢(shì),大多數(shù)寫法相差不多。
我們這里選擇Jest來分享,其他測(cè)試框架可以自行了解。
3、單元測(cè)試適用的測(cè)試對(duì)象有哪些?
-
1、常見工具類函數(shù) -
2、公共子組件 -
3、接口請(qǐng)求數(shù)據(jù)
二、給項(xiàng)目配置Jest
1、安裝
yarn add -D jest或npm install jest -D
2、配置(非必需)
如果你想獲得更多的jest配置,可以增加配置文件。
比如項(xiàng)目中的Jest,默認(rèn)不顯示測(cè)試覆蓋率和測(cè)試報(bào)告等,想要支持,就需要我們將Jest的配置文件暴露出來,只需要執(zhí)行yarn test --init或npx jest --init
然后根據(jù)需求選擇對(duì)應(yīng)的配置,最后會(huì)在根目錄下生成jest.config.js文件
根據(jù)提示選擇即可,這里我們選擇JsDom環(huán)境,需要代碼測(cè)試覆蓋率報(bào)告,自動(dòng)清除每個(gè)單元測(cè)試之間的模擬調(diào)用和實(shí)例。
執(zhí)行完成后,發(fā)現(xiàn)在項(xiàng)目根目錄下多了一個(gè)jest.config.js文件,里面包含了各種配置說明
module.exports = {
// 是否顯示覆蓋率報(bào)告
collectCoverage: true,
// 告訴 jest 文件測(cè)試要求的閾值,單位為百分比
// coverageThreshold: {
// global: {
// statements: 90, // 每行
// functions: 80, // 每個(gè)函數(shù)
// branches: 90 // 分支覆蓋率
// }
// }
}
此時(shí)再次執(zhí)行單元測(cè)試,發(fā)現(xiàn)顯示了測(cè)試覆蓋率
用瀏覽器打開coverage目錄下的index.html,可以看到此時(shí)頁面顯示了測(cè)試報(bào)告
3、配置快速執(zhí)行命令
package.json
{
"scripts": {
"start": "node index.js",
"test": "jest",
"coverage": "jest --coverage"
}
}
執(zhí)行命令啟動(dòng)單元測(cè)試yarn test或yarn coverage
4、項(xiàng)目配置
一般通過腳手架生成的項(xiàng)目,已經(jīng)默認(rèn)配置了測(cè)試框架,比如React的項(xiàng)目配置了
Jest,Vue3.x項(xiàng)目默認(rèn)配置了Vitest。
-
Jest默認(rèn)支持Commonjs
-
如果你的項(xiàng)目不支持ESM,需要安裝 @babel/core,@babel/preset-env進(jìn)行轉(zhuǎn)譯。 -
如果你的項(xiàng)目需要支持TS,可以 @types/jest、@babel/preset-typescript
執(zhí)行yarn test發(fā)現(xiàn)報(bào)錯(cuò),是因?yàn)樾枰渲?code style="color: rgb(30, 107, 184);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">babel
配置babel
安裝插件,在根目錄下新建
.babelrc文件
// .babelrc
{
"plugins": [
[
"@babel/plugin-syntax-jsx"
]
],
"presets": [ "@babel/preset-env", "@babel/preset-react" ]
}
注意
-
如何支持或忽略.css文件 -
如何忽略單行、函數(shù)或文件、目錄
5、快速上手單元測(cè)試
比如我們有sum.js
export function sum(a, b){
return a + b;
}
export function mins(a, b){
return a - b;
}
為這個(gè)函數(shù)寫測(cè)試文件
import { sum, mins } from './sum'
it(`should add 1 + 2 to equal 3`, () => {
expect(sum(1, 2)).toBe(3);
});
test(`mins 2 - 1 to equal 1`, () => {
expect(min(2, 1)).toBe(1);
})
入門很簡單,只需要針對(duì)每個(gè)函數(shù)做一些預(yù)期的校驗(yàn)即可,當(dāng)不小心改動(dòng)了源代碼導(dǎo)致輸出的結(jié)果和預(yù)期不符,將會(huì)測(cè)試不通過,這樣就保證了代碼功能的穩(wěn)定。
describe、test預(yù)留字段基本沒有區(qū)別,描述方式不同,一個(gè)it should,另一個(gè)test action
三、項(xiàng)目中如何開始寫單元測(cè)試
寫單元測(cè)試要考慮清楚幾點(diǎn):
-
測(cè)試的主要目的不是證明代碼的正確,而是為了發(fā)現(xiàn)錯(cuò)誤。 -
測(cè)試代碼,只考慮外部接口,不考慮內(nèi)部實(shí)現(xiàn) -
充分考慮數(shù)據(jù)的邊界條件 -
對(duì)重點(diǎn)、核心代碼重點(diǎn)測(cè)試 -
減少測(cè)試代碼數(shù)量,避免無用功 -
基于需求寫單元測(cè)試
1、在項(xiàng)目根目錄下新建tests目錄,將單元測(cè)試文件放在其中,測(cè)試文件命名
xx.test.js,優(yōu)點(diǎn)是可以更好的管理測(cè)試文件,缺點(diǎn)是不好找到源文件2、在對(duì)應(yīng)文件的目錄下新建
__test__目錄,測(cè)試文件放置其中,優(yōu)點(diǎn)就是容易找到執(zhí)行文件,但不容易過濾和管理
1、給工具函數(shù)寫單元測(cè)試
給工具函數(shù)寫測(cè)試函數(shù)是單元測(cè)試很重要的一個(gè)場(chǎng)景,我們以金額千分位格式化處理函數(shù)為例,通過單元測(cè)試發(fā)現(xiàn)問題。
// 將數(shù)字千分位格式化后返回對(duì)應(yīng)的字符串
export function getThousandFormatNum(num) {
const str = num + '';
const reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+.)/g : /(\d{1,3})(?=(\d{3})+(?:$|.))/g;
return str.replace(reg, '$1,');
}
單元測(cè)試
import { getThousandFormatNum } from './common'
describe('getThousandFormatNum', () => {
// 常規(guī)數(shù)字格式化
it('should return a string with thousand format', () => {
expect(getThousandFormatNum(1000)).toBe('1,000');
expect(getThousandFormatNum(1000000)).toBe('1,000,000');
expect(getThousandFormatNum(123456789)).toBe('123,456,789');
});
// 格式化后和本身相同的數(shù)字
it('should return the same number if it is not greater than 999', () => {
expect(getThousandFormatNum(0)).toBe('0');
expect(getThousandFormatNum(999)).toBe('999');
});
// 格式化負(fù)數(shù)
it('should handle negative numbers correctly', () => {
expect(getThousandFormatNum(-1000)).toBe('-1,000');
expect(getThousandFormatNum(-1000000)).toBe('-1,000,000');
expect(getThousandFormatNum(-123456789)).toBe('-123,456,789');
});
// 格式化帶小數(shù)的數(shù)字
it('should handle decimal numbers correctly', () => {
expect(getThousandFormatNum(1234.56)).toBe('1,234.56');
expect(getThousandFormatNum(1234567.89)).toBe('1,234,567.89');
});
});
執(zhí)行單元測(cè)試
2、給組件寫單元測(cè)試(快照測(cè)試)
前端主要就是組件,但業(yè)務(wù)組件變動(dòng)比較頻繁,所以傾向于給公共組件或組件庫增加單元測(cè)試,防止組件擴(kuò)展或變更導(dǎo)致業(yè)務(wù)Bug。
我們以APP.js組件為例,寫單元測(cè)試,并生成快照。
function App() {
return (
<div className="App">
<HashRouter basename="/">
<div style={{marginBottom: 20}}>
<Link style={{marginRight: 20}} to="/">Home更新版本1</Link>
<Link to="/about">About更新版本123</Link>
</div>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<AFunction />}></Route>
<Route path="/about" element={<BFunction />} />
</Routes>
</Suspense>
</HashRouter>
</div>
);
}
export default App;
給App.js寫單元測(cè)試
import { render, screen, act } from '@testing-library/react';
import App from './App';
test('renders learn react link', async () => {
let tree;
await act(async () => {
tree = render(<App />);
})
const linkElement = screen.getByText(/About更新版本123/i);
expect(linkElement).toBeInTheDocument();
expect(tree).toMatchSnapshot();
});
當(dāng)我們改動(dòng)App.js,單元測(cè)試發(fā)現(xiàn)上個(gè)版本的快照更新了,就會(huì)報(bào)錯(cuò),提醒檢查,如果更改沒問題,可以執(zhí)行u更新快照
3、模擬函數(shù)(Mock)
Mock是單元測(cè)試中很重要的一部分,他一般在下面場(chǎng)景中使用
-
模擬數(shù)據(jù) -
模擬接口請(qǐng)求 -
模擬定時(shí)器,比如setTimout 1小時(shí),那每次測(cè)試花費(fèi)一小時(shí)就瘋了 -
組件使用Redux怎么測(cè)試
在組件中,經(jīng)常有一些引用的變量
const mock = jest.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
模擬接口請(qǐng)求
test('async test', async () => {
const asyncMock = jest.fn().mockResolvedValue(43); // Promise
await asyncMock(); // 43
});
模擬函數(shù)有很多,在實(shí)際使用過程中需要各種結(jié)合使用,這里僅展示了最簡單的使用。
4、常用斷言方法
在工具函數(shù)測(cè)試過程中,我們常常要判斷變量類型和值,測(cè)試框架往往提供了判斷方法,下面是Jest一些常見的判斷,更多可以查閱官網(wǎng)[3]
toBe:判斷測(cè)試結(jié)果為某個(gè)值
not:否定判斷
test('the best flavor is not coconut', () => {
expect(bestLaCroixFlavor()).toBe('coconut');
});
test('the best flavor is not coconut', () => {
expect(bestLaCroixFlavor()).not.toBe('coconut');
});
toEqual:檢測(cè)引用類型,遞歸檢查屬性和屬性值
toEqual會(huì)調(diào)用Object.is方法,toBe ===
const can1 = {
flavor: 'grapefruit',
ounces: 12,
};
const can2 = {
flavor: 'grapefruit',
ounces: 12,
};
describe('the La Croix cans on my desk', () => {
test('have all the same properties', () => {
expect(can1).toEqual(can2); // true
});
test('are not the exact same can', () => {
expect(can1).not.toBe(can2); // true
});
});
toMatch:匹配字符串規(guī)則,正則匹配
describe('an essay on the best flavor', () => {
test('mentions grapefruit', () => {
expect(essayOnTheBestFlavor()).toMatch(/grapefruit/);
expect(essayOnTheBestFlavor()).toMatch(new RegExp('grapefruit'));
});
});
toBeTruthy:匹配if條件為真
drinkSomeLaCroix();
if (thirstInfo()) {
drinkMoreLaCroix();
}
四、查看單元測(cè)試的結(jié)果
單元測(cè)試完成后,執(zhí)行測(cè)試命令
yarn test 或 npx jest
1、測(cè)試覆蓋率解讀
-
Stmts (Statements):語句覆蓋率,即被測(cè)試覆蓋的代碼語句的百分比。在你的代碼中,92.85% 的語句被測(cè)試覆蓋。 -
Branch:分支覆蓋率,即被測(cè)試覆蓋的條件分支的百分比。在你的代碼中,100% 的分支被測(cè)試覆蓋。 -
Funcs (Functions):函數(shù)覆蓋率,即被測(cè)試覆蓋的函數(shù)的百分比。在你的代碼中,83.33% 的函數(shù)被測(cè)試覆蓋。 -
Lines:行覆蓋率,即被測(cè)試覆蓋的代碼行數(shù)的百分比。在你的代碼中,100% 的行被測(cè)試覆蓋。 -
Uncovered Line:未覆蓋的行號(hào)。這一列列出了未被測(cè)試覆蓋的代碼行的行號(hào)范圍。
2、測(cè)試信息解讀
-
Test Suites: 2 passed, 2 total:這表示你有 2 個(gè)測(cè)試套件,其中所有的 2 個(gè)測(cè)試套件都通過了。 -
Tests: 8 passed, 8 total:這表示你一共運(yùn)行了 8 個(gè)測(cè)試,其中所有的 8 個(gè)測(cè)試都通過了。 -
Snapshots: 1 total:這表示1個(gè)快照測(cè)試(Snapshot Testing)。 -
Time: 2.703 s:這表示測(cè)試運(yùn)行的時(shí)間為 2.703 s 秒。
3、參考
-
Jest官網(wǎng)[4] -
Jest實(shí)踐指南[5]
Node 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點(diǎn)贊、在看” 支持一下
