前端單元測試實踐
一說到單元測試,可能對於業務一線同學來說,心理立馬就會無形中有一種壓迫感,心想 “業務都做不完了,寫個球的單元測試,先保證功能完備,趕緊上線才是王道”,這句話的核心是以業務為重,沒任何問題,但是,業務在任何時候都是重要的,除了業務,其實還有效率。
沒有效率,就沒有生產力,沒有生產力就沒法給業務鋪墊更廣闊的道路,效率如此重要,那我們該從哪些維度來提升效率呢?從筆者的個人經驗來看,不管是在什麼領域,我們在提效道路上一定會經歷以下幾個階段:
- 規範標準化
- 機器自動化
- 系統平臺化
- 人工智慧化
要經歷以上過程,必須要有程式碼質量的保證,如果我們不關注程式碼質量,我們的研發效率是沒法做到質的飛越的,原因很簡單,就是人類在解決各種問題的過程中,總會不由自主的引入其他問題,從而導致系統穩定性降低,如何在漫長的系統維護過程中,保證每次釋出的程式碼質量則是我們一直在持續探索的方向。所以現在軟體測試在高校裡都有專門的學科,同時軟體測試崗位在網際網路公司裡也是非常常見的,可見,企業對系統穩定性的要求是非常非常高的。說了那麼多,下面直接進入正題。
什麼是單元測試?
單元測試,是指對軟體中的最小可測試單元進行檢查和驗證,也就是說一個測試單元往往是一個原子型函式,同時,單元測試的編寫者必須是作者本人,擁有單元測試的程式有以下幾個好處:
1、它是一種驗證行為
程式中的每一項功能都是測試來驗證它的正確性。它為以後的開發提供支援。就算是開發後期,我們也可以輕鬆的增加功能或更改程式結構,而不用擔心這個過程中會破壞重要的東西。而且它為程式碼的重構提供了保障。這樣,我們就可以更自由的對程式進行改進。
2、它是一種設計行為
編寫單元測試將使我們從呼叫者觀察、思考。特別是先寫測試(Test First),迫使我們把程式設計成易於呼叫和可測試的,即迫使我們解除軟體中的耦合。
3、它是一種編寫文件的行為
單元測試是一種無價的文件,它是展示函式或類如何使用的最佳文件。這份文件是可編譯、可執行的,並且它保持最新,永遠與程式碼同步。
4、它具有迴歸性
自動化的單元測試避免了程式碼出現迴歸,編寫完成之後,可以隨時隨地的快速執行測試。
單元測試用例設計
任何一個單元測試都應該包含:
- 正常輸入
- 離散覆蓋引數值域
- 邊界輸入
- 空值驗證
- 零值驗證
- 最大值驗證
- 非法輸入
- 入引數據型別非法
- 記憶體溢位驗證
冪等
對於單元測試來說,保證其冪等性非常重要,冪等就是在相同輸入的前提下,其輸出結果不隨時間而改變。
所以,我們可以看到,對於函數語言程式設計語言來說,寫單元測試則是非常容易的事情,因為在函式式正規化中,我們的函式都是純函式,在正規化層面上就已經約束了開發者寫出冪等的程式,那麼,在javascript領域,我們想要寫出質量更高,對測試友好的程式碼的話,則需要儘可能的寫出各種純函式,從而保證冪等性。
對於前端而言,其實還包含UI介面的冪等,如何更加高效的保證介面冪等,我們是可以藉助jest的快照能力實現html結構級別的冪等驗證或者通過gemini的離線截圖能力來實現畫素級的冪等驗證。
Mock
- Mock資料,在編寫單元測試用例的過程中,構造Mock資料是非常重要的實現手段,因為構造資料就是我們在構造輸入的過程,比如正常輸入/邊界輸入/非法輸入
- Mock環境,對於前端自動化測試而言,我們的環境Mock,往往是通過jsdom之類的庫實現環境mock,保證離線場景下可以驗證依賴瀏覽器API的程式邏輯
- Mock事件,對於離線場景來說人機互動事件是不會有真實人類參與的,所以,我們需要Mock人機互動事件,幫助程式邏輯實現UI介面的互動功能性測試,在React中,是可以通過enzyme來實現Mock事件
- Mock模組/第三方包,有些場景我們的程式依賴了某些第三方包,但是第三方包會引入副作用,比如axios,如果被測試的程式使用了該模組,它會走真實的發請求邏輯,這樣還需要開一個mock請求服務,如果有一個模組攔截Mock能力,我們就不需要再開一個mock請求服務了,恰好jest提供了模組mock的能力,對於這類問題便可以輕鬆解決。
- Mock函式/類,在Javascript語言中,函式的入參同樣也可以是函式(匿名函式),這恰好是Js最靈活的地方,但是如果引數是函式,則會使得測試用例的編寫難度大大提升,我們很難知道入參函式的呼叫情況,所以,如果我們可以跟蹤入參函式呼叫情況,就能很輕鬆的驗證函數語言程式設計正規化下的程式邏輯,恰好jest提供了一個函式Mock能力,可以幫助使用者快速Mock一個可以跟蹤其呼叫情況的匿名函式。同樣,對於類也是,jest提供了mock類的能力,幫助使用者跟蹤一個類例項的使用過程。
白盒覆蓋
白盒覆蓋就是測試用例要儘可能的覆蓋程式內部的所有分支語句,從而整體性的保證程式碼質量。
我們都知道,覆蓋率是衡量單元測試質量的核心指標,但是,對於TDD而言,我們肯定不可能做到一開始就達到100%的覆蓋率,所以,正常的單元測試用例,往往是先從黑盒用例來寫,也就是程式對外暴露的API層面的測試,前期先將這部分的單測覆蓋全,後期,我們在bugfix或者feature addtion的過程中可以逐步增加測試用例,最終逐步達到80%以上的覆蓋率即可滿足白盒覆蓋的效果。
單測定級
根據我們前面所述的白盒覆蓋,覆蓋率是一個非常客觀的指標,但是覆蓋率對於開發者的認知模型而言是不夠清晰結構化的,所以,我們還需要對覆蓋率再做一次結構化定級,方便開發者一步步完善單元測試,下面讓我們來列舉一下所有的單測級別:
- Level1:正常流程可用,即一個函式在輸入正確的引數時,會有正確的輸出
- Level2:異常流程可丟擲邏輯異常,即輸入引數有誤時,不能丟擲系統異常,而是用自己定義的邏輯異常通知上層呼叫程式碼其錯誤之處
- Level3:極端情況和邊界資料可用,對輸入引數的邊界情況也要單獨測試,確保輸出是正確有效的
- Level4:所有分支、迴圈的邏輯走通,不能有任何流程是測試不到的
- Level5:輸出資料的所有欄位驗證,對有複雜資料結構的輸出,確保每個欄位都是正確的
自動化單元測試
其實前面已經提到過了,Jest,就是一款自動化單元測試解決方案,它基本上滿足了前端單元測試的所有測試需求,而且它還是一款零配置解決方案,顧名思義,就是最簡單場景下是無需任何配置即可快速編寫測試用例。所以使用Jest,前端寫測試用例就變得十分容易了。本文不會介紹具體jest該如何使用,它有哪些API,因為此類文章到處都能找到,本文更多的是從測試方法論出發探討單元測試的實施方案。
提高測試用例編寫效率
有了Jest,我們在寫單元測試用例的配置成本已經很低了,所以,單元測試的成本,更多的是編寫測試用例上,
要提高測試用例編寫效率,我們主要從幾個方向來提高:
- 定製標準用例模板,讓開發者做填空題,而非選擇題
- 制定單元測試開發規範,幫助開發者寫出統一一致的單元測試用例,也方便後續協同開發維護
- 漸進式編寫測試用例,藉助bugfix/feature addtion過程逐步完善測試用例,最大化減輕前期時間壓力
React元件單元測試規範
1. 測試檔案統一在src/__tests__
目錄中維護
主要是Follow Facebook的目錄命名規範
2. 測試檔案命名與React元件命名保持一致,後面以.spec.js結尾
主要是Follow Facebook的測試檔案命名規範,比如:Form.spec.js
3. 測試用例使用test("功能描述",()=>{})函式描述用例單元
針對最小功能單元的測試用例主要集中在該函式內
4. 一組功能集合測試使用describe("功能集合描述",()=>{})函式描述功能集合
一個測試檔案只能描述一個功能集合,這個功能集合可以是一個React元件,也可以是一個cjs模組
5. UI測試套件統一使用enzyme
使用enzyme可以藉助jquery like的選擇器方便的對DOM渲染結果做校驗
6. React元件測試用例必須包含
- API屬性覆蓋性測試用例
- DOM快照比對,冪等校驗
- 私有Utils函式測試用例,千萬不能忽略Utils函式的測試用例,很多時候,bug就出在這上面
7. 對DOM結構做用例校驗
一個標準的React元件測試用例的輸入往往是元件配置或互動事件,輸出則是具體的DOM結構,我們的用例校驗也都是對DOM結構做用例校驗
8. bugfix/feature addtion必須要有對應的單元測試用例才能釋出
9. 團隊協作,MR/PR必須要有對應的單元測試用例才能釋出
參考資料
https:// jestjs.io jest官網
https:// github.com/sapegin/jest -cheat-sheet jest快速上手教程
https:// github.com/jest-communi ty/awesome-jest jest周邊生態彙總