從零開始搞基建(4)——單元測試


  單元測試有助於避免尷尬、耗時的錯誤,將測試作為安全網只是一部分,更大部分是將測試表達為代碼的思考過程。

  接下來的內容提煉自《單元測試的藝術(第2版)》和《有效的單元測試》兩本書。

一、質疑和回答

       在組內推廣時,進度並不理想,遇到的阻礙大致可歸納為以下這幾種情況:

  • 首先就是團隊成員會質疑單元測試的價值,需要給出證明單元測試確實有效可行的方法和證據。
  • 其次是團隊成員缺乏主動測試的意識,目前有大量的測試代碼,不知道從哪里開始測試,並且花費額外的精力來維護單元測試的代碼。
  • 還有就是編寫單元測試大概會花費日常開發的 10% 以上的時間,而項目時間總是比較緊,無法留出充裕的測試時間。

       針對第一個問題,可用一個實驗回答。讓兩個在技術和經驗上近似的團隊,分別負責兩個規模近似的項目,其中一個進行單元測試,另一個不進行單元測試的時間差。

       下表是兩個團隊的進度和輸出度量:

 
階段
不進行單元測試的團隊
進行單元測試的團隊
實施 7天 14天
集成 7天 2天
測試和修復缺陷

測試:3天

修復:3天

測試:3天

修復:2天

測試:1天

總計:12天

測試:3天

修復:1天

測試:1天

修復:1天

測試:1天

總計:7天

整體交付時間 26天 23天
客戶發現的缺陷數 71 11

       針對第二個問題,可引用一份研究報告,20世紀70和90年代進行的研究表明:通常,20%的代碼包含了90%的缺陷。如果能找到這20%的代碼,那么就能大大提升測試效率。

       但困難的就是如何找到包含最多問題的代碼。其實任何團隊都能告訴你哪個組件問題最多,那你就可以從這個組件開始測試。

       針對第三個問題,目前的辦法是多寫多測,讓單元測試成為開發的一部分,不要苛求測試覆蓋率,先就測試影響業務流程的核心代碼。

二、測試替身

       測試替身的作用是隔離被測代碼,加速執行測試,使執行變得確定,模擬特殊情況,暴露隱藏信息。

       其中隔離被測代碼,使測試有針對性和容易理解,而利用測試替身實現的隔離,還有個副作用,那就是測試替身的速度要比本尊快很多。

       測試替身的類型:

  • Stub(測試樁):一個對象的所有方法只有一行,且各自返回一個適當的默認值。使用場景:只關心協作對象輸送的響應。
  • Fake(偽造對象):可以返回硬編碼值,而每個測試可能需要有差異地實例化來返回不同值,模擬不同場景。使用場景:所依賴的服務或組件無法供測試使用,打樁產生了難以維護的糟糕代碼。
  • Spy(測試間諜):用於記錄過去發生的情況,這樣測試在事后就知道所發生的一切。使用場景:將其作為參數傳遞被測函數中。
  • Mock(模擬對象):是特殊的Spy,在一個特定情景下可配置行為的對象。使用場景:關心某些交互,即兩個對象之間的方法調用。

三、設計指南

  • 避免測試中包含邏輯,不應該有switch、if-else等判斷語句,for、while等循環語句。以免測試難以閱讀和理解,難以復現,難以命名。
  • 只測試一個關注點,一個工作單元只有一個最終結果,例如一個返回值、系統狀態的一個改變或對第三方對象的一個調用。
  • 專注檢查行為而非實現,避免過度指定,只需檢查最終行為的正確性即可,既不要使用多個模擬對象,也不要對一個被測對象的純內部狀態進行斷言。
  • 避免復雜的私有方法,不要直接測試 private 方法。
  • 避免在構造函數中包含需要測試的代碼邏輯。
  • 避免單例,單例模式會妨礙創建不同的變體。
  • 使用 new 時要當心,實例化的對象,應該僅限於不會替換為測試替身的對象。

四、測試壞味道

1)可讀性

       程序員用測試的方式來表達和驗證代碼的假設和預期行為。

       閱讀測試代碼之后,就該理解代碼應當做什么。程序員運行那些測試時,就該了解代碼實際上在做什么。

  1. 問題:基本斷言缺乏意義,因為斷言的基本原理和意圖隱藏在看上去無意義的單詞和數字背后,造成難以理解。
  2. 改進:去掉魔法數字,改用斷言方法,使用編程語言內置的 API 語法。
  1. 問題:過度斷言很脆弱,並且掩蓋了整體廣度和深度之下的意圖。
  2. 改進:識別無關細節並移除。
  1. 問題:人格分裂是指一個測試檢查了多件事。
  2. 改進:去掉重復,將粗粒度的場景分離。
  1. 問題:過分保護是指在測試開頭增加守衛語句和空值檢查保護自己。
  2. 改進:去除冗余斷言,檢查要使真正的斷言通過所需的中間條件。

2)可維護性

       代碼從不慢慢退化,而是直接奔潰。

       測試也是如此,同樣脆弱,程序員編寫自動化的單元測試來盡可能地管理這種脆弱性。

       大家都知道維護噩夢是什么,你絕對不希望你的測試代碼淪落其中。

  1. 問題:重復最明顯的是某一個數字或字符串在代碼中反復出現。
  2. 改進:將可變數據提煉到局部變量中。
  1. 問題:殘缺的文件路徑會使代碼無法轉移,只能在某個人的計算機中。
  2. 改進:避免絕對路徑,選擇相對路徑,用流來替換文件。
  1. 問題:像素完美出現的場景包括期望和實際產生的圖像完美匹配。
  2. 改進:用適當的抽象層次來表達測試,將背后的細節隱藏到自定義斷言中,進行模糊匹配。

3)可信度

       軟件開發其實就是在修改、演進和維護代碼,如果不能信任測試,那么在即使看似最無辜的改動之后,仍然不能確信代碼是否能夠工作。

       接下來會圍繞測試不可靠的問題來檢閱測試壞味道。

  1. 問題:永不失敗的測試不具有價值,給你虛假的安全感。
  2. 改進:養成運行測試的習慣,例如臨時修改被測代碼來故意觸發一次失敗。
  1. 問題:輕率承諾是指測試實際上沒有測試任何東西,或名不符實。
  2. 改進:確保斷言了一些事情,確切找出要檢查的行為,也更容易命名。
  1. 問題:降低期望就是降低了確定性與精確性的標准。
  2. 改進:提高門檻,使測試的期望更具體。
  1. 問題:有條件的測試是在一個測試方法內隱藏了秘密條件,使測試邏輯名不符實。
  2. 改進:確保測試在每個條件分支時都有機會失敗。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM