目前開(kāi)發(fā)大型應(yīng)用缀辩,測(cè)試是一個(gè)非常重要的環(huán)節(jié)臭埋,但是大多數(shù)前端開(kāi)發(fā)者對(duì)測(cè)試相關(guān)的知識(shí)是比較缺乏的。因?yàn)榭赡茼?xiàng)目開(kāi)發(fā)周期短根本沒(méi)有機(jī)會(huì)寫臀玄,所以你沒(méi)有辦法體會(huì)到前端自動(dòng)化測(cè)試的重要性瓢阴。
來(lái)說(shuō)說(shuō)為什么前端自動(dòng)化測(cè)試如此重要!
先看看前端常見(jiàn)的問(wèn)題:
- 修改某個(gè)模塊功能時(shí)健无,其它模塊也受影響荣恐,很難快速定位bug
- 多人開(kāi)發(fā)代碼越來(lái)越難以維護(hù)
- 不方便迭代,代碼無(wú)法重構(gòu)
- 代碼質(zhì)量差
增加自動(dòng)化測(cè)試后:
- 我們?yōu)楹诵墓δ芫帉憸y(cè)試后可以保障項(xiàng)目的可靠性
- 強(qiáng)迫開(kāi)發(fā)者編寫更容易被測(cè)試的代碼,提高代碼質(zhì)量
- 編寫的測(cè)試有文檔的作用叠穆,方便維護(hù)
1.測(cè)試簡(jiǎn)介
1.1 黑盒測(cè)試和白盒測(cè)試
- 黑盒測(cè)試一般也被稱為功能測(cè)試少漆,黑盒測(cè)試要求測(cè)試人員將程序看作一個(gè)整體,不考慮其內(nèi)部結(jié)構(gòu)和特性硼被,只是按照期望驗(yàn)證程序是否能正常工作
- 白盒測(cè)試是基于代碼本身的測(cè)試示损,一般指對(duì)代碼邏輯結(jié)構(gòu)的測(cè)試。
1.2 測(cè)試分類
單元測(cè)試(Unit Testing)
單元測(cè)試是指對(duì)程序中最小可測(cè)試單元進(jìn)行的測(cè)試嚷硫,例如測(cè)試一個(gè)函數(shù)
检访、一個(gè)模塊
、一個(gè)組件
...
集成測(cè)試(Integration Testing)
將已測(cè)試過(guò)的單元測(cè)試函數(shù)進(jìn)行組合集成暴露出的高層函數(shù)或類的封裝论巍,對(duì)這些函數(shù)或類進(jìn)行的測(cè)試
端到端測(cè)試(E2E Testing)
打開(kāi)應(yīng)用程序模擬輸入烛谊,檢查功能以及界面是否正確
1.3 TDD & BDD
TDD是測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(Test-Driven Development)
TDD的原理是在開(kāi)發(fā)功能代碼之前,先編寫單元測(cè)試用例代碼
BDD是行為驅(qū)動(dòng)開(kāi)發(fā)(Behavior-Driven Development)
系統(tǒng)業(yè)務(wù)專家嘉汰、開(kāi)發(fā)者丹禀、測(cè)試人員一起合作,分析軟件的需求鞋怀,然后將這些需求寫成一個(gè)個(gè)的故事双泪。開(kāi)發(fā)者負(fù)責(zé)填充這些故事的內(nèi)容,保證程序?qū)崿F(xiàn)效果與用戶需求一致密似。
小結(jié):
TDD是先寫測(cè)試再開(kāi)發(fā) (一般都是單元測(cè)試焙矛,白盒測(cè)試);而B(niǎo)DD則是按照用戶的行為來(lái)開(kāi)發(fā)残腌,再根據(jù)用戶的行為編寫測(cè)試用例 (一般都是集成測(cè)試村斟,黑盒測(cè)試)
1.4 測(cè)試框架
- Karma:Karma為前端自動(dòng)化測(cè)試提供了跨瀏覽器測(cè)試的能力,可以在瀏覽器中執(zhí)行測(cè)試用例
- Mocha:前端自動(dòng)化測(cè)試框架抛猫,需要配合其他庫(kù)一起使用蟆盹,像chai、sinon...
- Jest:Jest 是Facebook推出的一款測(cè)試框架闺金,集成了 Mocha,chai,jsdom,sinon等功能逾滥。
- ...
看到這里Facebook
都在推Jest,你還不學(xué)嗎? Jest也有一些缺陷就是不能像Karma
這樣直接跑在瀏覽器上败匹,它采用的是jsdom
寨昙,優(yōu)勢(shì)是簡(jiǎn)單、0配置掀亩! 后續(xù)我們通過(guò)Jest來(lái)聊聊前端自動(dòng)化測(cè)試舔哪。
2.Jest的核心應(yīng)用
在說(shuō)Jest
測(cè)試之前,先來(lái)看看以前我們是怎樣測(cè)試的
const parser = (str) =>{
const obj = {};
str.replace(/([^&=]*)=([^&=]*)/g,function(){
obj[arguments[1]] = arguments[2];
});
return obj;
}
const stringify = (obj) =>{
const arr = [];
for(let key in obj){
arr.push(`${key}=${obj[key]}`);
}
return arr.join('&');
}
// console.log(parser('name=webyouxuan')); // {name:'webyouxuan'}
// console.log(stringify({name:'webyouxuan'})) // name=webyouxuan
我們每寫完一個(gè)功能归榕,都先需要手動(dòng)測(cè)試功能是否正常尸红,測(cè)試后可能會(huì)將測(cè)試代碼注釋起來(lái),這樣會(huì)產(chǎn)生一系列問(wèn)題。因?yàn)闀?huì)污染源代碼外里,所有的測(cè)試代碼和源代碼混合在一起怎爵。如果刪除掉,下次測(cè)試還需要重新編寫盅蝗。
所以測(cè)試框架就幫我們解決了上述的問(wèn)題
2.1 分組鳖链、用例
Jest是基于模塊
的,我們需要將代碼包裝成模塊的方式墩莫,分別使用 export
將 parser
芙委、stringify
這兩個(gè)方法導(dǎo)出。
安裝jest
npm init -y # 初始化pacakge.json
npm i jest
我們建立一個(gè)qs.test.js
來(lái)專門編寫測(cè)試用例狂秦,這里的用例你可以認(rèn)為就是一條測(cè)試功能 (后綴要以.test.js結(jié)尾灌侣,這樣jest測(cè)試時(shí)默認(rèn)會(huì)調(diào)用這個(gè)文件)。
import {parser,stringify} from './qs';
it('測(cè)試 parser 是否能正常解析結(jié)果',()=>{
// expect 斷言裂问,判斷解析出來(lái)的結(jié)果是否和 {name:'webyouxuan'}相等
expect(parser(`name=webyouxuan`)).toEqual({name:'webyouxuan'});
})
jest
默認(rèn)自帶斷言功能侧啼,斷言的意思就是判斷是不是這個(gè)樣子,我斷定你今天沒(méi)吃飯~堪簿,結(jié)果你吃了痊乾,說(shuō)明這次斷言就失敗了,測(cè)試就無(wú)法通過(guò)椭更。
通過(guò)配置scripts
來(lái)執(zhí)行命令
"scripts": {
"test": "jest"
}
執(zhí)行 npm run test
哪审,可惜的是默認(rèn)在node
環(huán)境下不支持es6模塊
的語(yǔ)法,需要babel
轉(zhuǎn)義虑瀑,當(dāng)然你也可以直接使用commonjs規(guī)范來(lái)導(dǎo)出方法湿滓,因?yàn)榇蠖鄶?shù)現(xiàn)在開(kāi)發(fā)都采用es6模塊,所以安裝一下即可舌狗。
# core是babel的核心包 preset-env將es6轉(zhuǎn)化成es5
npm i @babel/core @babel/preset-env --save-dev
并且需要配置.babelrc
文件茉稠,告訴babel用什么來(lái)轉(zhuǎn)義
{
"presets":[
[
"@babel/preset-env",{
"targets": {"node":"current"}
}
]
]
}
默認(rèn)Jest中集成了babel-jest
,運(yùn)行時(shí)默認(rèn)會(huì)調(diào)用.babelrc
進(jìn)行轉(zhuǎn)義把夸,可以直接將es6轉(zhuǎn)成es5語(yǔ)法。
運(yùn)行 npm run test
出現(xiàn):
繼續(xù)編寫第二個(gè)用例
import {parser,stringify} from './qs';
describe('測(cè)試qs 庫(kù)',()=>{
it('測(cè)試 parser 是否能正常解析結(jié)果',()=>{
expect(parser(`name=webyouxuan`)).toEqual({name:'webyouxuan'});
});
it('測(cè)試 stringify 是否正常使用stringify',()=>{
expect(stringify({name:'webyouxuan'})).toEqual(`name=webyouxuan`)
})
});
describe的功能是給用例分組铭污,這樣可以更好的給用例分類恋日,其實(shí)這就是我們所謂的單元測(cè)試,對(duì)某個(gè)具體函數(shù)和功能進(jìn)行測(cè)試嘹狞。
2.2 matchers匹配器
在寫第一個(gè)測(cè)試用例時(shí)岂膳,我們一直在使用toEqual
其實(shí)這就是一個(gè)匹配器,那我們來(lái)看看jest
中常用的匹配器有哪些磅网?因?yàn)槠ヅ淦魈嗔颂附兀晕揖椭v些常用的!
為了方便理解,我把匹配器分為三類:判斷相等簸喂、不等毙死、是否包含。
it('判斷是否相等',()=>{
expect(1+1).toBe(2); // 相當(dāng)于 js中的===
expect({name:'webyouxuan'}).toEqual({name:'webyouxuan'}); // 比較內(nèi)容是否相等
expect(true).toBeTruthy(); // 是否為 true / false 也可以用toBe(true)
expect(false).toBeFalsy();
});
it('判斷不相等關(guān)系',()=>{
expect(1+1).not.toBe(3); // not取反
expect(1+1).toBeLessThan(5); // js中的小于
expect(1+1).toBeGreaterThan(1); // js中的大于
});
it('判斷是否包含',()=>{
expect('hello world').toContain('hello'); // 是否包含
expect('hello world').toMatch(/hello/); // 正則
});
2.3 測(cè)試操作節(jié)點(diǎn)方法
說(shuō)了半天喻鳄,我們自己來(lái)寫個(gè)功能測(cè)試一下!
export const removeNode = (node) => {
node.parentNode.removeChild(node)
};
核心就是測(cè)試傳入一個(gè)節(jié)點(diǎn)扼倘,這個(gè)節(jié)點(diǎn)是否能從DOM
中刪除
import { removeNode } from './dom'
it('測(cè)試刪除節(jié)點(diǎn)',()=>{
document.body.innerHTML = `<div><button data-btn="btn"></button</div>`
let btn = document.querySelector('[data-btn="btn"]')
expect(btn).not.toBeNull()
removeNode(btn);
btn = document.querySelector('[data-btn="btn"]');
expect(btn).toBeNull()
})
這個(gè)就是我們所說(shuō)的jsdom
,在node中操作DOM元素
2.4 Jest常用命令
我們希望每次更改測(cè)試后除呵,自動(dòng)重新執(zhí)行測(cè)試再菊,修改執(zhí)行命令:
"scripts": {
"test": "jest --watchAll"
}
重新執(zhí)行 npm run test
,這時(shí)就會(huì)監(jiān)控用戶的修改