GO語言學習筆記-測試篇 Study for Go ! Chapter ten- Test

slowlydance2me發表於2023-03-14

持續更新 Go 語言學習進度中 ......

  1. GO語言學習筆記-型別篇 Study for Go! Chapter one - Type - slowlydance2me - 部落格園 (cnblogs.com)
  2. GO語言學習筆記-表示式篇 Study for Go ! Chapter two - Expression - slowlydance2me - 部落格園 (cnblogs.com)
  3. GO語言學習筆記-函式篇 Study for Go ! Chapter three - Function - slowlydance2me - 部落格園 (cnblogs.com)

Study for Go ! Chapter ten- Test

 

1. 單元測試 ( unit test )

單元測試 ( unit test )除用來測試邏輯演算法是否符合預期外,還承擔著監控程式碼質量的責任。任何時候都可以用簡單的命令來驗證全部功能,找出未完成任務 ( 驗收 ) 和任何因修改而造成的錯誤。它與效能測試、程式碼覆蓋率等一起保證了程式碼總是在可控範圍內,這遠比形式化的人工檢查要有用得多

單元測試並非要取代人工程式碼審查 ( code review ),實際上它也無法切入到程式碼實現層面。但可透過測試結果為審查提供篩選資料,避免因繁瑣導致程式碼審查淪為形式主義。單元測試可自動化進行,能持之以恆。但測試畢竟只是手段,而非目的,所以如何合理安排測試就需要開發人員因地制宜

可以將測試、版本管理工具、以及自動釋出 (nightly build)整合。編寫指令碼將測試失敗結果與程式碼提交日誌相匹配,最終生成報告發往指定郵箱

很多人認為單元測試程式碼不好寫,不知道怎麼測試。如果非技術原因,那麼需要考慮結構設計是否合理,因為可測試性也是程式碼質量的一個體現

寫單元測試本身就是對即將要實現的演算法做複核預演。因為無論什麼演算法都需要給如條件,返回預期結果。這些加上平時寫在 main 裡面的臨時程式碼,本就是一個完整的單元測試用例,無非換個地方存放而已

 

Testing

  • 工具鏈和標準庫自帶單元測試框架,這讓測試工作變得相對容易。關於測試,有以下規則:

    • 測試程式碼須放在當前包以“ _test.go ” 結尾的檔案中

    • 測試函式以 Test 為名稱字首

    • 測試命令 ( go test )忽略以 “ _ ” 或 “ . ” 開頭的測試檔案

    • 正常編譯操作 ( go build/install )會忽略測試檔案

  • 標準庫 testing 提供了專用型別 T 來控制測試結果和行為

  • 使用 Parallel 可有效利用多核並行優勢,縮短測試時間

 

對於測試是否應該和目標放在同一目錄,一直有不同的看法。某些人認為應該另建一個專門的包用來存放單元測試,且只測試目標公開介面。好處是,當目標內部發生變化時,無須同步維護測試程式碼。每個人對於測試都有不同的理解,就像覆蓋率是否要做到 90% 以上,也是見仁見智

 

Table driven

  • 單元測試程式碼一樣要寫的簡介優雅,要做到這一點並不容易。好在多數時候,我們可以用一種類似資料表的模式來批次輸入條件並依次比對結果

  • 這種方式將測試資料和測試邏輯分離,更便於維護。另外,使用 Error 是為了讓整個表全部完成測試,以便知道具體是哪組條件出了問題。

 

Test main

  • 某些時候,須為測試用例提供初始化和清理操作,但 testing 並沒有 setup / teardown 機制。解決辦法是自定義一個名為 TestMain 的函式,go test 會改為執行該函式,而不再是具體的測試用例

  • M.Run 會呼叫具體的測試用例,但麻煩的是不能為每一個測試檔案寫一個 TestMain

  • 要實現用例組合套件 ( suite ),需藉助 MainStart 自行構建 M 物件。透過與命令列引數相配合,即可實現不同測試組合

 

Example

  • 例程式碼最大的用途不是測試,而是匯入到 GoDoc 等工具生成的幫助文件中。它透過比對輸出 ( stdout )結果和內部 output 註釋是否一致來判斷是否成功

  • 如果沒有 output 註釋,函式就不會被執行。另外,不能使用內建函式 print/println 因為它們輸出到 stderr

 

 

2. 效能測試

  • 效能測試函式以 Benchmark 為名稱字首,同樣儲存在 “ *_test.go ” 檔案裡

  • 測試工具預設不會執行效能測試,須使用 bench 引數。它透過逐步調整 B.N 值反覆執行測試函式,知道獲得準確的測量結果

  • 如果僅希望進行效能測試,可以用 run = NONE 忽略所有單元測試用例

  • 某些耗時的目標,預設迴圈次數過少,取平均值不足以準確計量效能。可使用 benchtime 設定最小測試時間來增加迴圈次數,以便返回更準確的結果

 

Timer

  • 如果在測試函式中要執行一些額外操作,那麼應該臨時阻止計時器工作

 

Memory

  • 效能測試關心的不僅僅是執行時間,還包括在堆上的記憶體分配,因為記憶體分配和垃圾回收的相關操作也應該計入消耗成本

  • 也可以將測試函式設定為總是輸出記憶體分配資訊,無論使用 benchmem 引數與否

 

3. 程式碼覆蓋率

  • 如果說單元測試和效能測試關注程式碼質量,那麼程式碼覆蓋率 ( code coverage)就是度量測試自身完整和有效性的一種手段

  • 透過覆蓋率值,我們可以分析出測試程式碼的編寫質量。檢測它是否提供了足夠的測試條例,是否執行了足夠的函式、語句、分支、和程式碼行等,依次來量化測試本身,讓白盒測試真正起到應有的質量保障作用。

  • 當然,這並不是說要追求形式上的數字百分比。關鍵還是為改進測試提供一個可發現缺陷的機會,畢竟只有測試本身的質量得到保障,才能讓它免於成為形式主義擺設

  • 程式碼覆蓋率也常用來發現死程式碼 ( dead code )

  • 為獲取更詳細的資訊,可指定 covermode 和 coverprofile 引數

    • set :是否執行

    • count: 執行次數

    • atomic:執行次數 (支援併發模式)

    • 還可以在瀏覽器中檢視包括具體的執行次數等資訊

 

 

4. 效能監控

  • 引發效能問題的原因無外乎執行時間過長,記憶體佔用過多,以及意外阻塞。透過捕獲或監控相關執行狀態資料,就可定位引發問題的原因,從而有針對性改進演算法

  • 有兩種捕獲方式:首先,在測試時輸出並儲存相關資料,進行初級評估。其次,在執行階段透過 Web 介面獲得實時資料,分析一段時間內的健康情況。除此之外,我們還可以用自定義計數器 ( expvar )提供更多與邏輯相關的參考資料

相關文章