最近幾個月來,筆者一直在探索前端UI自動化測試的場景和方案。最初的時候,面對眾多的技術(shù)選型,顯得有些茫然,而團隊此前也沒有太多關(guān)于這方面的經(jīng)驗,只能一步一步摸索總結(jié),當然期間也踩過不少坑,最終形成了一套相對穩(wěn)定的測試方案,未來還將繼續(xù)擴展和完善。
項目地址:jest-puppeteer-testing
在這篇文章中,筆者想和大家分享一下自己對于UI自動化測試的思考和經(jīng)驗。
業(yè)務(wù)的更新迭代頻繁,傳統(tǒng)測試大部分都還是手工、肉眼的模式來進行,無法滿足產(chǎn)品敏捷開發(fā)、快速迭代的需求。而UI自動化能讓全功能的回歸變得簡單,釋放純手工測試的人力資源,并且回歸測試能夠覆蓋到所有的邏輯場景,這對測試的效率,以及整個開發(fā)流程的效率都是很大的提升,并且能夠規(guī)避很多人的主觀和客觀因素導(dǎo)致的漏測或者疏忽。
其他測試方式的局限性:
單元測試(Unit Testing)
事實上,單元測試確實能夠幫助我們發(fā)現(xiàn)大部分的問題,但是在復(fù)雜的前端交互中,單純的單元測試并不能真實地反映用戶操作的路徑,而單元測試一般的場景是測試一系列的功能集合。
快照測試(Snapshot Testing)
DOM結(jié)構(gòu)并不能完全反映頁面的視覺效果,DOM結(jié)構(gòu)不變并不完全等于樣式不變。此外,大多數(shù)工具都是React專用,非React應(yīng)用基本不支持。
筆者想說:
很多人認為,UI總是頻繁的變動,導(dǎo)致測試用例維護成本高,性價比低,因此UI自動化測試比較適合場景穩(wěn)定的業(yè)務(wù)。其實不是,這里的UI不僅僅指的是視覺,更多的是業(yè)務(wù)邏輯。UI可以多變,但業(yè)務(wù)邏輯一定是趨于穩(wěn)定的,尤其是核心業(yè)務(wù),想一想用戶得多辛苦才能適應(yīng)這種業(yè)務(wù)邏輯頻繁變更的產(chǎn)品啊。
TypeScript + Jest + Puppeteer
事實上,對于UI自動化測試來說,許多框架之間并沒有太多差別,也從來不是影響整套測試用例是否健壯的關(guān)鍵性因素。相比之下,如何提高測試用例穩(wěn)定性及全面性才是讓UI自動化測試方案落地的重要細節(jié)。
大家可以參考jest-puppeteer-testing,這里不再累述。
// setup/expect-image-snapshot.ts// 讓jest支持保存/比對屏幕截圖import { configureToMatchImageSnapshot } from 'jest-image-snapshot';expect.extend({ toMatchImageSnapshot: configureToMatchImageSnapshot({ customSnapshotsDir: '__image_snapshots__', }),});
// setup/enhance-puppeteer.ts// 增強puppeteer功能,如:攔截請求并使用mock數(shù)據(jù)import { onRequestInterceptor } from '../utils/request';jest.setTimeout(30000);beforeAll(async () => { page.on('request', onRequestInterceptor); // 攔截請求,使用代理數(shù)據(jù) await page.setRequestInterception(true);});
// utils/request.ts// mock數(shù)據(jù)的核心文件// 這里只攔截xhr或fetch請求,當然你也可以自行擴展import { URL } from 'url';import { Request } from 'puppeteer';import mocks from '../mocks';// 設(shè)置請求攔截器的數(shù)據(jù),用于同一請求返回不同結(jié)果,生效一次后自動銷毀export const interceptors: { [api: string]: any } = {};export const setRequestInterceptor = (api: string, value: any) => { interceptors[api] = value;};export const onRequestInterceptor = (request: Request) => { const resourceType = request.resourceType(); if (resourceType === 'xhr' || resourceType === 'fetch') { const location = new URL(request.url()); const mockKey = location.pathname; if (mockKey && mocks.hasOwnProperty(mockKey)) { const mock = mocks[mockKey]; let response: any; if (typeof mock === 'function') { response = mock({ location, request, interceptor: interceptors[mockKey] }); delete interceptors[mockKey]; // 生效一次后自動銷毀 } else { response = mock; } if (response) { if (response.body != null && typeof response.body === 'object') { response.body = JSON.stringify(response.body); } request.respond(response); } } else { request.continue(); } } else { request.continue(); }};
shared.d.ts
定義數(shù)據(jù)類型cases
目錄下存放測試用例mocks
目錄下存放mock數(shù)據(jù)utils
目錄下存放工具方法補充說明:關(guān)于mock的類型定義,可以在shared.d.ts中找到,當然你也可以在這里增加其他類型定義
測試地址的選擇(本地/線上)
盡量抹平不確定因素帶來的影響
如維持數(shù)據(jù)請求的結(jié)果穩(wěn)定,日期時間穩(wěn)定,保證頁面渲染的一致性。假如由于數(shù)據(jù)返回或時間的不確定性,導(dǎo)致每次頁面渲染不一樣,那這樣測試也失去了意義。
盡量明確保存屏幕截圖的時機
如訪問一個頁面后截圖,由于網(wǎng)絡(luò)因素的原因,圖片資源并不是每次都加載完成,從而導(dǎo)致截圖前后不一樣。
......(暫時寫這么多,有空再更)
事實上,這套UI自動化測試方案更像是端到端測試(E2E Testing),即模擬一個用戶將程序作為一個完全的黑盒,打開應(yīng)用程序模擬輸入,檢查功能以及界面是否正確,配合屏幕截圖可以直觀感受到用戶進行某些交互產(chǎn)生的具像化視覺效果。
項目地址:jest-puppeteer-testing