一說到單元測試,可能對於業務一線同學來說,心理立馬就會無形中有一種壓迫感,心想 「業務都做不完了,寫個球的單元測試,先保證功能完備,趕緊上線纔是王道」,這句話的核心是以業務為重,沒任何問題,但是,業務在任何時候都是重要的,除了業務,其實還有效率。

沒有效率,就沒有生產力,沒有生產力就沒法給業務鋪墊更廣闊的道路,效率如此重要,那我們該從哪些維度來提升效率呢?從筆者的個人經驗來看,不管是在什麼領域,我們在提效道路上一定會經歷以下幾個階段:

  • 規範標準化
  • 機器自動化
  • 系統平臺化
  • 人工智慧化

要經歷以上過程,必須要有代碼質量的保證,如果我們不關注代碼質量,我們的研發效率是沒法做到質的飛越的,原因很簡單,就是人類在解決各種問題的過程中,總會不由自主的引入其他問題,從而導致系統穩定性降低,如何在漫長的系統維護過程中,保證每次發布的代碼質量則是我們一直在持續探索的方向。所以現在軟體測試在高校裏都有專門的學科,同時軟體測試崗位在互聯網公司裏也是非常常見的,可見,企業對系統穩定性的要求是非常非常高的。說了那麼多,下面直接進入正題。

什麼是單元測試?

單元測試,是指對軟體中的最小可測試單元進行檢查和驗證,也就是說一個測試單元往往是一個原子型函數,同時,單元測試的編寫者必須是作者本人,擁有單元測試的程序有以下幾個好處:

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必須要有對應的單元測試用例才能發布

參考資料

jestjs.io jest官網

github.com/sapegin/jest jest快速上手教程

github.com/jest-communi jest周邊生態匯總

zhuanlan.zhihu.com/p/28 jest中文教程

testanything.org/tap-sp TAP測試報告規範


推薦閱讀:
相關文章