新聞中心
作者 | 張霄翀

目前創(chuàng)新互聯(lián)建站已為上千多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機(jī)、網(wǎng)站托管運(yùn)營、企業(yè)網(wǎng)站設(shè)計(jì)、龍江網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
前言
我曾經(jīng)在好幾個(gè)項(xiàng)目里都近乎完整參與過補(bǔ)齊前端測試的工作,也收集到不同項(xiàng)目的同事很多關(guān)于前端測試的困惑和痛點(diǎn),這其中大部分都很相似,我也感同身受,在這篇文章里,我會(huì)針對大家和自己常遇到的痛點(diǎn)分享一些自己的經(jīng)驗(yàn),如果你也有如下相似的困擾,那希望這篇文章能對你有些幫助~
常見問題(排名不分先后):
- 前端測試感覺寫起來很復(fù)雜,會(huì)花很多時(shí)間,甚至經(jīng)常是業(yè)務(wù)代碼時(shí)間的好幾倍
- 前端測試怎么TDD?
- 測試一些第三方UI控件時(shí),特別難模擬與之的交互
- 有些東西不知道怎么mock,比如時(shí)間,瀏覽器全局變量(window.location,local storage)等
- 測試?yán)餃?zhǔn)備數(shù)據(jù)的代碼特別長,真正的測試代碼很靠后,要翻很久,不容易定位
- 跑測試時(shí)會(huì)冒出很多Error或Warn Log,好像不影響測試通過,修起來也很花時(shí)間,還用修么?
?
在分享問題的相關(guān)經(jīng)驗(yàn)之前,我們先來梳理一下前端測試體系~
前端測試體系
前端測試的重要性
這其實(shí)跟所有測試的重要性是一樣的,大家有這么多的痛點(diǎn)也是因?yàn)橹栏采w全面的測試可以對代碼質(zhì)量更有保證,讓我們更有信心地去重構(gòu)代碼,也能幫助我們更方便地了解現(xiàn)有的功能細(xì)節(jié),甚至是一些極端的邊界情況。而且在大家合作開發(fā)項(xiàng)目代碼的過程中,測試可以幫助我們更早地發(fā)現(xiàn)錯(cuò)誤,減少時(shí)間成本,提高交付效率。
前端測試方法論(TDD vs. BDD)
這兩個(gè)常見的測試方法論在這里簡單介紹一下,就不大篇幅展開了。TDD - (Test-Driven Development 測試驅(qū)動(dòng)開發(fā))簡單地說就是先根據(jù)需求寫測試用例,然后實(shí)現(xiàn)代碼,通過后再接著寫下一個(gè)測試和實(shí)現(xiàn),循環(huán)直到全部功能和重構(gòu)完成?;舅悸肪褪峭ㄟ^測試來推動(dòng)整個(gè)開發(fā)的進(jìn)行。BDD - (Behavior Driven Development 行為驅(qū)動(dòng)開發(fā)) 其實(shí)可以看做是TDD的一個(gè)分支。簡單地說就是先從外部定義業(yè)務(wù)行為,也就是測試用例,然后由外入內(nèi)的實(shí)現(xiàn)這些行為,最后得到的測試用例也是相應(yīng)業(yè)務(wù)行為的驗(yàn)收標(biāo)準(zhǔn)。
前端測試的分層
在這里借一下前端大牛Kent C. Dodds的獎(jiǎng)杯分層法來引出常見的分類:
(圖片出處:https://kentcdodds.com/blog/static-vs-unit-vs-integration-vs-e2e-tests)
端到端測試 End to End Test
端到端測試一般會(huì)運(yùn)行在完整的應(yīng)用系統(tǒng)上(包括前端和后端),包含用戶完整的使用場景,比如打開瀏覽器,從注冊或登錄開始,在頁面內(nèi)導(dǎo)航,完成系統(tǒng)提供的功能,最后登出。
有時(shí),我們也會(huì)在這里引入可視化用戶界面測試,即一種通過像素級比較屏幕截屏來驗(yàn)證頁面顯示是否正確的測試。目的是確保界面在不同設(shè)備、瀏覽器、分辨率和操作系統(tǒng)下與預(yù)期的樣式一致。可以設(shè)置一定的偏差容忍值。這一層的測試成本較高,所以通常重心會(huì)放在確保主流程的功能正常上。常用工具:Cypress、Playwright、Puppeteer、TestCafe、Nightwatch (下載量對比)
集成測試 Integration Test
集成測試主要是測試當(dāng)單元模塊組合到一起之后是否功能正常。在不同的測試上下文下可能有不同的定義,在前端測試這里通常指測試集成多個(gè)單元組件到一起的組件。
單元測試 Unit Test
單元測試就是對沒有依賴或依賴都被mock掉了的測試單元的測試。在前端代碼里,它可能是:
- 沒有依賴或依賴都被mock掉了的單元組件
- 功能代碼如Utils/Helpers等公共方法集合的測試
- 輔助組件功能如React Hook / Selector等公共方法的測試
靜態(tài)代碼測試 Static Test
主要是指利用一些代碼規(guī)范工具(Lint Tool)來及時(shí)捕獲代碼中潛在的語句錯(cuò)誤,統(tǒng)一代碼格式等。這里就不展開了。常見工具和實(shí)踐有:
- Eslint + Prettier 代碼規(guī)范和樣式統(tǒng)一
- husky + lint-staged (gitHooks工具)可以自動(dòng)在commit和push之前進(jìn)行代碼掃描,阻止不規(guī)范代碼進(jìn)入代碼庫,也可以設(shè)置在push之前跑一遍前端測試
前端測試策略
還是這張圖,我標(biāo)記了一下:
- 越往上成本越高
- 越往上得到反饋的速度越慢
- 但越往上,越貼近最終用戶的行為,越能發(fā)現(xiàn)真實(shí)的問題,能給到的信心就更多
在獎(jiǎng)杯的形狀上每一層占的面積代表了應(yīng)該投入的重心比例。
這里集成測試的比重比單元測試大是因?yàn)榧蓽y試可以在成本很高的e2e測試和離最終用戶行為較遠(yuǎn)的單元測試之間取的一個(gè)平衡,它可以寫的很接近最終用戶的行為,成本又相對的沒那么高,屬于性價(jià)比很高的一部分。
所以集成測試有一些原則:
- 可以根據(jù)每個(gè)頁面的復(fù)雜程度決定是只有一個(gè)全頁面的集成測試還是可以劃分成幾大塊分別有集成測試,但一旦作為集成測試,就要盡可能的少mock依賴,盡量的渲染全子組件
- 盡量測試用戶的所見和交互,而不是背后的實(shí)現(xiàn),否則就會(huì)遠(yuǎn)離最終用戶行為,降低信心值,而且隨著代碼的重構(gòu),測試也需要頻繁的修改。比如Enzyme可以把component里的方法、props、state等都提供出來單獨(dú)測試,但這里的測試并不貼近真實(shí)用戶的交互,很容易就會(huì)因?yàn)橹貥?gòu)而破壞測試,更好的方法是真的去測試當(dāng)props和state變化后頁面的變動(dòng),或交互的變化
- 準(zhǔn)備的測試數(shù)據(jù)盡量豐富且貼近真實(shí)數(shù)據(jù)(用戶敏感信息要替換掉),越貼近真實(shí)的數(shù)據(jù)越能覆蓋到更多真正的問題
- 對于核心的業(yè)務(wù)行為,要重點(diǎn)測試
對于單元測試來說:
- UI組件類的測試:因?yàn)橛辛思蓽y試的覆蓋,可以簡單的測試一下不同props的渲染,如果有一些集成測試覆蓋不到的特殊數(shù)據(jù)引發(fā)的交互行為,可以測試一下
- 非UI組件類的測試:通常會(huì)覆蓋一些復(fù)雜的業(yè)務(wù)邏輯,需要全面的測試一下不同的分支條件
前端測試工具的分類
測試啟動(dòng)工具 (Test Launchers)
測試啟動(dòng)工具負(fù)責(zé)將測試運(yùn)行在Node.js或?yàn)g覽器環(huán)境。形式可能是CLI或UI,并結(jié)合一定的配置。常見工具有:Jest / Karma / Jasmine / Cypress / TestCafe 等。
測試結(jié)構(gòu)工具 (Structure Providers)
測試結(jié)構(gòu)工具提供一些方法和結(jié)構(gòu)將測試組織的更好,擁有更好的可讀性和可擴(kuò)展性。如今,測試結(jié)構(gòu)通常以BDD形式來組織。測試結(jié)構(gòu)如下方Jest例子:
// Jest test structure
describe('calculator', () => {
// 第一層級: 標(biāo)明測試的模塊名稱
beforeEach(() => {
// 每個(gè)測試之前都會(huì)跑,可以統(tǒng)一添加一些mock等
})
afterEach(() => {
// 每個(gè)測試之后都會(huì)跑,可以統(tǒng)一添加一些清理功能等
})
describe('add', () => {
// 第二層級: 標(biāo)明測試的模塊功能分組
test('should add two numbers', () => {
// 實(shí)際的描述業(yè)務(wù)需求的測試
...
})
})
})
常見工具有:Jest / Mocha / Cucumber / Jasmine / Cypress / TestCafe 等。
斷言庫 (Assertion Functions)
斷言庫會(huì)提供一系列的方法來幫助驗(yàn)證測試的結(jié)果是否符合預(yù)期。如下方的例子:
// Jest expect (popular)
expect(foo).toEqual('bar')
expect(foo).not.toBeNull()
// Chai expect
expect(foo).to.equal('bar')
expect(foo).to.not.be.null
常見工具有:Jest / Chai / Assert / TestCafe 等。
Mock工具
有的時(shí)候我們在測試的時(shí)候需要隔離一些代碼,模擬一些返回值,或監(jiān)控一些行為的調(diào)用次數(shù)和參數(shù),比如網(wǎng)絡(luò)請求的返回值,一些瀏覽器提供的功能,時(shí)間計(jì)時(shí)等,Mock工具會(huì)幫助我們更容易的去完成這些功能。
常見工具有:Sinon / Jest (spyOn, mock, useFakeTimers…) 等。
快照測試工具 (Snapshot Comparison)
快照測試對于UI組件的渲染測試十分有效。原理是第一次運(yùn)行時(shí)生成一張快照文件,需要開發(fā)人員確認(rèn)快照的正確性,之后每一次運(yùn)行測試都會(huì)生成一張快照并與之前的快照做比較,如果不匹配,則測試失敗。這時(shí)如果新的快照確實(shí)是更新代碼后的正確內(nèi)容,則可以更新之前保存的快照。(這里的快照通常都是框架渲染器生成的序列化后的字符串,而不是真實(shí)的圖片,這樣的測試效率比較高)。
這里可以參考Jest官方的用例。
常見工具有:Jest / Ava / Cypress
測試覆蓋率工具(Test Coverage)
測試覆蓋率工具可以產(chǎn)出測試覆蓋率報(bào)告,通常會(huì)包含行、分支、函數(shù)、語句等各個(gè)維度的代碼覆蓋率,還可以生成可視化的html報(bào)告來可視化代碼覆蓋率。如以下的Jest內(nèi)置的代碼覆蓋率報(bào)告:
(圖片出處:??https://jestjs.io/)??
常見工具有:Jest內(nèi)置 / Istanbul。
E2E 測試工具(End to End Test)
上面在測試分層里介紹過的。
可視化用戶界面測試(Visual Regression)
也在上面的測試分層里介紹過。通常會(huì)和e2e測試工具組合在一起使用,一般主流的e2e測試工具也會(huì)有對應(yīng)的庫去進(jìn)行可視化用戶界面測試。
前端框架專屬測試庫
不同的前端框架還會(huì)有一些自帶的或推薦的測試庫,比如:
- React: React官方的Test Utils / Testing Library - React(推薦) / Enzyme (基于上面的測試策略,更推薦React Testing Library,Enzyme暴露了太多內(nèi)部元素用來測試,雖然一時(shí)方便,但遠(yuǎn)離了用戶行為,之后帶來的修改頻率也比較高,性價(jià)比低)
- Vue: Vue官方的Test Utils / Testing Library - Vue
- Angular: Angular內(nèi)置的測試框架(Jasmine) / Testing Library - Angular
前端測試框架
基于上面的分類,大家可能發(fā)現(xiàn)幾乎哪哪都有Jest,這類大而全的前端測試工具我們也可以稱為前端測試框架。
常見的有:
- Jest:大力推薦,幾乎有測試需要的所有工具,社區(qū)活躍,網(wǎng)上資源豐富,也是React官方推薦的測試框架
- Mocha:雖然也功能豐富,但沒有斷言庫、測試覆蓋率工具和Mock工具,需要和其他第三方庫配合使用
- Jasmine:比較老派的工具,功能也沒有Jest豐富,下載率逐年下降
最后附上一張stateOfJS網(wǎng)站2021年的測試庫滿意度圖表供大家參考 :
(圖片出處:https://2021.stateofjs.com/en-US/libraries/testing/)
前端測試的常見問題
終于回到最開始的問題了,分享一下我的經(jīng)驗(yàn)和通常的解決辦法:
前端測試感覺寫起來很復(fù)雜,會(huì)花很多時(shí)間,甚至經(jīng)常是業(yè)務(wù)代碼時(shí)間的好幾倍,這個(gè)問題可以分成三部分來下手:
優(yōu)化測試策略
可以根據(jù)剛才的測試策略部分,結(jié)合自己項(xiàng)目的實(shí)際情況,調(diào)整一下在不同的測試層分配的重心,定一下自己項(xiàng)目每個(gè)層級的測試粒度,這樣才能在保證交付的前提下達(dá)到測試信心值收益的最大化。
提升寫測試效率
(1) 抽取公共的部分,使具體的測試文件簡潔
- 準(zhǔn)備數(shù)據(jù)的fixture庫,可以輕松的生成想要的store數(shù)據(jù)或請求返回?cái)?shù)據(jù)
- 公共的render方法,可以支持自定義store, stub子組件, mock框架全局方法等
- 公共的第三方UI組件交互方法,可以輕松的觸發(fā)第三方控件的事件,不用再關(guān)心實(shí)現(xiàn)細(xì)節(jié)
- 公共的api mock方法,可以在測試文件里不用關(guān)心api細(xì)節(jié),輕松mock
(2) 統(tǒng)一測試規(guī)范,有優(yōu)化及時(shí)重構(gòu)所有測試,這樣大家可以放心的參考已有測試,不會(huì)有多種寫法影響可讀性
提升運(yùn)行測試的效率
- 并行跑測試
- 測試?yán)锍S萌缦路椒ㄊ勾郎y的異步請求返回,通常也會(huì)給setTimeout一個(gè)等待時(shí)間,大部分的情況0就可以達(dá)到目的了,除非是邏輯真的要等待一定的時(shí)間,如果默認(rèn)值都設(shè)置的比較大,每個(gè)測試都會(huì)耽誤一些時(shí)間,加起來對測試運(yùn)行性能的影響是很大的
// testUtils.js
export const flushPromises = (interval = 0) => {
return new Promise((resolve) => {
setTimeout(resolve, interval);
});
};
// example.test.js
test('should show ...', async () => {
//render component
await flushPromises();
//verify component
});
前端測試怎么TDD
通常問這個(gè)問題背后隱藏的問題是前端很難先寫測試,再寫實(shí)現(xiàn)。確實(shí)我也有同感,如果是一些util/helper方法是可以很容易的遵循TDD的步驟的,但當(dāng)涉及頁面結(jié)構(gòu)和樣式的時(shí)候,很難在寫測試的時(shí)候就想清楚頁面到底有哪些具體的元素,用到哪些需要mock的模塊。
所以在測試UI組件時(shí),我通常會(huì)使用BDD的方式,具體步驟是:
- 建立組件文件,渲染返回空
- 建立測試文件,先寫一個(gè)snapshot測試,測試會(huì)通過,生成一個(gè)snapshot文件
- 再根據(jù)這個(gè)頁面mockup上已知的交互寫好test case,通常這個(gè)時(shí)候不太容易寫實(shí)現(xiàn),就先把測試用例都寫好,test先skip起來,eslint可以設(shè)置成skip的test用warn來展示,這樣之后方便補(bǔ)全
// Jest
describe('todo component', () => {
test('should show todo list', () => {
// Snapshot test
const tree = renderer.create().toJSON();
expect(tree).toMatchSnapshot();
})
test.skip('should add todo when click add and input todo content', () => {
})
test.skip('should remove todo when click delete icon of todo item', () => {
})
- 隨著頁面重構(gòu),可能會(huì)給組件添加props,這時(shí)也需要給不同的props添加snapshot測試或交互測試
- 最后可以根據(jù)測試跑完的測試覆蓋率報(bào)告看看是否覆蓋全面了,防止有遺漏
當(dāng)然隨著前端代碼寫的越來越熟練,為了提升效率,有時(shí)會(huì)簡化步驟,等一個(gè)小功能的組件都重構(gòu)完了,樣式調(diào)好了,所有的子組件都抽完了,再根據(jù)每個(gè)組件的props和交互的點(diǎn)批量加測試,最后用測試覆蓋率來驗(yàn)證是否都覆蓋到了,保證自己新寫的組件都盡可能是100%的覆蓋率。
測試一些第三方UI控件時(shí),特別難模擬與之的交互
這個(gè)是我也很頭疼的問題,有的時(shí)候一些第三方組件因?yàn)橐獙?shí)現(xiàn)一些復(fù)雜的效果,會(huì)使用不一樣的方式去監(jiān)聽事件。
比如我們有一個(gè)Vue項(xiàng)目上用到了element-ui的select組件,這個(gè)組件可以通過:remote-method 屬性開啟異步發(fā)請求加載選項(xiàng)的功能,測試?yán)锵肽M異步拿到選項(xiàng)后并選擇某選項(xiàng),就需要想辦法觸發(fā)它的@change 事件,通常一條await fireEvent.update(input, 'S'); 就搞定了,但這個(gè)怎么都不生效,仔細(xì)的查看它的實(shí)現(xiàn)才發(fā)現(xiàn)需要這么一串操作才能觸發(fā)到@change 事件。
const input = getByPlaceholderText('Please input to search');
await fireEvent.click(input);
await fireEvent.keyUp(input, { key: 'A', code: 'KeyA' });
await fireEvent.update(input, 'A');
await flushPromises(500); // 這個(gè)方法上面有介紹,的作用是讓異步的代碼返回結(jié)果,并且等待500ms,因?yàn)樵创a有500ms的等待,這里就也需要等待
await fireEvent.click(getByText('Apple'));這里我總結(jié)的經(jīng)驗(yàn)就是:
- 如果發(fā)現(xiàn)常用的交互方法不能生效,需要去研究第三方組件的源碼
- 更重要的是如果大家研究出來了方法,及時(shí)的把相關(guān)代碼抽到一個(gè)公共的util文件里,這樣之后就不會(huì)有人也花費(fèi)很多時(shí)間在上面了,確實(shí)經(jīng)常遇到大家重復(fù)卡在相同的第三方組件交互問題上而不知道已經(jīng)有代碼解決了的場景
有些東西不知道怎么mock,比如時(shí)間,瀏覽器全局變量(window.location,local storage)等
這個(gè)可以結(jié)合使用的測試工具去搜索,一般都會(huì)有很多現(xiàn)成的解決方案,在這里舉兩個(gè)例子:
Mock navigator.userAgent::
// jest.setup.js
Object.defineProperty(
global.navigator,
'userAgent',
((value) => ({
get() { return value; },
set(v) { value = v; },
}))(global.navigator['userAgent']),
);
// example.test.js
test('should show popup in Safari', () => {
global.navigator.userAgent = 'user agent of Safari ...';
// render and verify something
});
Mock window.open:
//jest.setup.js
Object.defineProperty(
window,
'open',
((value) => ({
get() { return value; },
set(v) { value = v; },
}))(window.open),
);
// example.test.js
test('should ...', () => {
window.open = jest.fn();
// render something
expect(window.open).toBeCalledWith('xxx', '_blank');
});
測試?yán)餃?zhǔn)備數(shù)據(jù),mock依賴的代碼特別長,真正的測試代碼很靠后,要翻很久,不容易定位
上面有介紹,可以將公共的部分抽取出去,又能減少代碼重復(fù),又能提升寫測試的效率,比如準(zhǔn)備數(shù)據(jù)的部分可以抽成公共的fixture文件,提供方法生成默認(rèn)的數(shù)據(jù),也可以通過參數(shù)去覆蓋修改部分?jǐn)?shù)據(jù),達(dá)到定制化的目的:
export const generateUser = (user = {}) => {
return {
id: 1,
firstName: 'San',
lastName: 'Zhang',
email: 'sanzhang@test.com',
...user,
};
};
跑測試時(shí)會(huì)冒出很多Error或Warn Log,好像不影響測試通過,修起來也很花時(shí)間,還用修么?
測試?yán)锏膱?bào)錯(cuò)通常都很有價(jià)值,需要重視。這里面的錯(cuò)誤有可能是:
- 前端框架相關(guān)的,比如被測的組件有寫的或調(diào)用的不合理的情況,這種有的時(shí)候不僅是測試調(diào)用組件方式的問題,有可能業(yè)務(wù)代碼也寫的有問題;或者是測試語句寫的不合理,如React的 not wrapped in act(...)
- 測試運(yùn)行相關(guān)的,比如有些請求沒有mock,測試?yán)镆恢钡炔坏椒祷刂刀鴗imeout了,但又不是主測的業(yè)務(wù),所以測試還是會(huì)通過,之前有遇到很多次測試并行跑時(shí)會(huì)互相影響,隨機(jī)掛,如果log里有類似這種timeout的內(nèi)容,很有可能就是原因,mock好了所有的請求后問題就解決了
雖然有的時(shí)候也會(huì)有一些由于第三方庫的原因引起的無法修復(fù)又沒有影響的log,可以忽略,但測試?yán)锎蟛糠志鍸og其實(shí)都是可以修復(fù)的,甚至在修復(fù)后可能得到意想不到的受益,比如發(fā)現(xiàn)真正業(yè)務(wù)代碼的問題,測試不再隨機(jī)掛了,測試運(yùn)行性能提升了等等。
總結(jié)
對于前端測試,我覺得重心不是機(jī)械的去追求測試覆蓋率,而是盡可能的在成本和信心值中間找到一個(gè)平衡,應(yīng)用一些好的實(shí)踐去降低寫測試的成本,提升寫測試帶來的回報(bào),讓大家對于項(xiàng)目質(zhì)量越來越有信心。
文章題目:前端測試體系和優(yōu)秀實(shí)踐
URL標(biāo)題:http://www.5511xx.com/article/dpogjhg.html
其他資訊
- 為什么電腦截屏變成黑色了?(windows10截圖黑屏怎么辦)
- 回收Linux系統(tǒng)的垃圾回收機(jī)制(linux垃圾)
- 常用的MySQL數(shù)據(jù)類型匯總
- 5g網(wǎng)絡(luò)名稱組成?(高性能網(wǎng)絡(luò)關(guān)鍵詞:云計(jì)算、物聯(lián)網(wǎng)、數(shù)據(jù)中心、軟件定義網(wǎng)絡(luò)、5G、網(wǎng)絡(luò)虛擬化、SDN、NFV、邊緣計(jì)算、網(wǎng)絡(luò)性能優(yōu)化、負(fù)載均衡、網(wǎng)絡(luò)安全、帶寬管理、QoS、延遲優(yōu)化、網(wǎng)絡(luò)拓?fù)?、流量控制、網(wǎng)絡(luò)監(jiān)控、容災(zāi)備份、網(wǎng)絡(luò)優(yōu)化)
- html5中如何設(shè)置文字透明


咨詢
建站咨詢
