ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Go 語言單元測試與效能測試實戰:用 testing 套件與 go test 守護 Web 應用程式碼品質

Go 語言單元測試與效能測試實戰:用 testing 套件與 go test 守護 Web 應用程式碼品質 文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载本篇文章以本書第 11 章「錯誤處理、除錯和測試」中的測試部分為核心完整講解 Go 語言內建testing測試框架與go test命令的實戰用法涵蓋單元測試的編寫規則、執行流程、結果判讀以及以Benchmark為代表的壓力效能測試方法。讀完本文後你將能為自己的 Go 套件尤其是 Web 應用中的核心邏輯建立一套可重複執行的單元測試與效能基準測試讓每次程式碼修改後都能透過一行go test快速完成回歸驗證。為什麼需要單元測試與效能測試開發程式其中很重要的一點是測試我們如何保證程式碼的品質如何保證每個函式是可執行的、執行結果是正確的又如何保證寫出來的程式碼效能是好的單元測試的重點在於發現程式設計或實現上的邏輯錯誤使問題及早暴露便於問題的定位與解決而效能測試壓力測試的重點則在於發現程式設計上的瓶頸讓上線的程式在高併發情境下依然保持穩定。Go 語言自帶一個輕量級的測試框架testing配合內建的go test命令即可實現單元測試與效能測試。testing框架與其他語言中的測試框架類似你可以基於這個框架編寫針對相應函式的測試案例也可以基於該框架編寫壓力測試案例。此外社群還提供了 gotests 外掛用於自動產生測試程式碼可透過以下命令安裝go get -u -v github.com/cweill/gotests/...安裝後即可利用它從既有函式簽名自動生成_test.go骨架降低手寫樣板程式碼的成本。如何編寫單元測試案例go test命令只能在相應的目錄下執行該目錄內的所有測試檔案因此我們先建立一個專案目錄gotest讓所有的程式碼與測試程式碼都放在同一個目錄下。步驟一建立被測套件 gotest.go在gotest目錄下建立第一個檔案gotest.go其中宣告了套件名稱gotest並提供一個執行除法運算的函式package gotest import ( errors ) func Division(a, b float64) (float64, error) { if b 0 { return 0, errors.New(除數不能為 0) } return a / b, nil }這個函式設計體現了本書 11.1 錯誤處理 中介紹的 Go 錯誤處理準則凡是可能出錯的 API 都回傳一個error變數呼叫方透過把回傳的error與nil比較來判定操作是否成功。這裡的errors.New(除數不能為 0)正是標準套件errors提供的錯誤構造方式——它把字串包裝成一個實現了內建error介面type error interface { Error() string }的物件。這樣設計的好處是除法函式在除數為 0 的邊界情況下不會產生Inf或NaN這類難以察覺的結果而是明確地返回錯誤讓測試與上層呼叫都能夠可靠地感知失敗。步驟二建立測試檔案 gotest_test.gogotest_test.go是我們的單元測試檔案。編寫 Go 測試檔案時必須記住以下原則檔名必須以_test.go結尾這樣執行go test時才會執行到相應的程式碼必須 importtesting這個套件所有的測試案例函式必須以Test開頭測試案例會按照原始碼中書寫的順序依次執行測試函式TestXxx(t *testing.T)的參數是testing.T我們可以使用該型別來記錄錯誤或測試狀態測試格式為func TestXxx(t *testing.T)其中Xxx部分可以是任意的字母數字組合但首字母不能是小寫字母 [a-z]例如Testintdiv是錯誤的函式名稱在測試函式中透過呼叫testing.T的Error、Errorf、FailNow、Fatal等方法來標記測試不通過呼叫Log方法記錄測試資訊。下面是本書提供的測試案例程式碼package gotest import ( testing ) func Test_Division_1(t *testing.T) { if i, e : Division(6, 2); i ! 3 || e ! nil { // try a unit test on function t.Error(除法函式測試沒通過) // 如果不是如預期的那麼就報錯 } else { t.Log(第一個測試通過了) //記錄一些你期望記錄的資訊 } } func Test_Division_2(t *testing.T) { t.Error(就是不通過) }這裡的Test_Division_1屬於典型的「表驅動思維」雛形先執行被測函式Division(6, 2)再同時斷言商值與錯誤兩個回傳值只要任一條件不符就呼叫t.Error讓測試失敗。Test_Division_2則故意寫死失敗用來示範測試失敗時的輸出樣式。步驟三執行 go test 並判讀結果在專案目錄下執行go test會顯示如下資訊--- FAIL: Test_Division_2 (0.00 seconds) gotest_test.go:16: 就是不通過 FAIL exit status 1 FAIL gotest 0.013s從結果可以看出測試沒有通過因為第二個測試函式中寫死了t.Error。那麼第一個函式的執行情況如何呢預設情況下執行go test不會顯示測試通過的資訊我們需要帶上參數go test -vverbose詳細模式就會顯示如下資訊 RUN Test_Division_1 --- PASS: Test_Division_1 (0.00 seconds) gotest_test.go:11: 第一個測試通過了 RUN Test_Division_2 --- FAIL: Test_Division_2 (0.00 seconds) gotest_test.go:16: 就是不通過 FAIL exit status 1 FAIL gotest 0.012s上面的輸出詳細展示了測試的過程測試函式 1Test_Division_1測試通過測試函式 2Test_Division_2測試失敗最後得出整體測試不通過的結論。注意-v模式輸出的幾個關鍵元素 RUN標記測試開始、--- PASS/FAIL給出單個案例的結果、括號內的0.00 seconds是該案例的執行耗時而失敗資訊會附帶gotest_test.go:16這樣的「檔案:行號」定位方便我們直接跳到出錯位置。步驟四修正失敗的測試案例接下來把測試函式 2 修改成真正驗證邊界條件的測試——檢查除數為 0 時是否如預期返回錯誤func Test_Division_2(t *testing.T) { if _, e : Division(6, 0); e nil { // try a unit test on function t.Error(Division did not work as expected.) // 如果不是如預期的那麼就報錯 } else { t.Log(one test passed., e) //記錄一些你期望記錄的資訊 } }然後執行go test -v顯示如下資訊測試通過了 RUN Test_Division_1 --- PASS: Test_Division_1 (0.00 seconds) gotest_test.go:11: 第一個測試通過了 RUN Test_Division_2 --- PASS: Test_Division_2 (0.00 seconds) gotest_test.go:20: one test passed. 除數不能為 0 PASS ok gotest 0.013s這次兩個測試案例全部PASS最終輸出ok gotest 0.013s代表整個套件測試通過且總耗時約 13 毫秒。Test_Division_2現在覆蓋了Division函式的錯誤分支——除數為 0 時必須返回非 nil 的 error這與gotest.go中errors.New(除數不能為 0)的實現互相印證形成完整的「正常路徑 錯誤路徑」雙分支覆蓋。testing.T 的常用方法速查在實際編寫測試時testing.T提供的核心方法各有分工本書提到的與常見的用法整理如下方法作用特點t.Log/t.Logf記錄測試資訊僅在-v模式或測試失敗時輸出t.Error/t.Errorf標記測試失敗並記錄訊息記錄後繼續執行後續程式碼t.Fatal/t.Fatalf標記測試失敗並記錄訊息呼叫後立即中止當前測試t.FailNow標記測試失敗並立即中止不輸出訊息通常配合其他記錄方法使用t.Skip跳過當前測試常用於依賴外部資源如資料庫暫時不可用的場景選擇Error還是Fatal的原則很簡單如果該失敗點之後的斷言仍有意義就繼續執行用Error否則例如初始化失敗、被測物件為 nil就立即停止用Fatal避免後續空指標恐慌淹沒真正的錯誤資訊。如何編寫壓力測試效能測試壓力測試用來檢測函式方法的效能與編寫單元功能測試的方法類似此處不再贅述但需要注意以下幾點壓力測試案例必須遵循如下格式其中XXX可以是任意字母數字的組合但首字母不能是小寫字母func BenchmarkXXX(b *testing.B) { ... }go test不會預設執行壓力測試的函式如果要執行壓力測試需要帶上參數-test.bench語法為-test.benchtest_name_regex例如go test -test.bench.*表示測試全部壓力測試函式在壓力測試案例中請記得在迴圈體內使用testing.B.N使測試可以正常執行——框架會根據設定的時間預算自動調整N的取值迴圈必須以b.N為上限才能得到可信的基準資料檔名也必須以_test.go結尾。下面我們建立一個壓力測試檔案webbench_test.go程式碼如下package gotest import ( testing ) func Benchmark_Division(b *testing.B) { for i : 0; i b.N; i { //use b.N for looping Division(4, 5) } } func Benchmark_TimeConsumingFunction(b *testing.B) { b.StopTimer() //呼叫該函式停止壓力測試的時間計數 //做一些初始化的工作例如讀取檔案資料資料庫連線之類的, //這樣這些時間不影響我們測試函式本身的效能 b.StartTimer() //重新開始時間 for i : 0; i b.N; i { Division(4, 5) } }b.StopTimer 與 b.StartTimer 的意義Benchmark_TimeConsumingFunction示範了一個重要的基準測試技巧如果測試函式之前需要做昂貴的初始化例如讀取檔案、建立資料庫連線、載入測試資料這些初始化耗時會污染基準結果讓效能資料失真。解法就是像上面的程式碼一樣在初始化前呼叫b.StopTimer()暫停計時完成初始化後再呼叫b.StartTimer()恢復計時再進入以b.N為上限的測量迴圈。這樣最後輸出的ns/op就是函式本身的純執行成本。執行壓力測試並解讀結果執行命令go test webbench_test.go -test.bench.*可以看到如下結果Benchmark_Division-4 500000000 7.76 ns/op 456 B/op 14 allocs/op Benchmark_TimeConsumingFunction-4 500000000 7.80 ns/op 224 B/op 4 allocs/op PASS ok gotest 9.364s上面的結果顯示我們沒有執行任何TestXXX的單元測試函式只執行了壓力測試函式。第一條顯示Benchmark_Division執行了 500000000 次5 億次每次執行的平均時間是 7.76 納秒每次執行配置 456 B/op、14 allocs/op第二條顯示Benchmark_TimeConsumingFunction執行了 500000000 次每次平均執行時間是 7.80 納秒每次配置 224 B/op、4 allocs/op。最後一行顯示測試總共的執行時間 9.364 秒。對照兩組資料可以發現Benchmark_TimeConsumingFunction透過StopTimer/StartTimer把初始化成本隔離在計時之外後其每次執行的記憶體配置次數allocs/op從 14 次降到 4 次——這正是效能測試的價值所在它能量化每次呼叫的耗時與記憶體開銷幫我們定位優化前後的差異。在 Web 應用場景中Handler 的每次請求處理都伴隨記憶體配置追蹤allocs/op對降低 GC 壓力、提升高併發下的吞吐量非常關鍵。若需要在基準測試中同時輸出記憶體分配統計可在函式開頭呼叫b.ReportAllocs()若被測操作本身極快、單次測量誤差大也可考慮在迴圈內手動累加多次操作後再交給b.N調控這都是testing.B提供的標準擴充手法。從本書第 11 章的脈絡看測試的定位本篇文章對應於本書第 11 章「錯誤處理、除錯和測試」的 11.3 小節。該章節的完整脈絡是11.1 錯誤處理 講解error型別設計與錯誤處理模式11.2 使用 GDB 除錯 講解執行期除錯本小節則負責測試——三者共同構成「寫出高品質程式碼」的閉環錯誤處理讓失敗可見、GDB 讓問題可定位、測試讓回歸可預防。正如 11.4 小結 所述一個好的 Web 應用必定有良好的錯誤處理機制、良好的單元測試與壓力測試以保證上線之後程式碼能夠保持良好效能並按預期運行。本書在 code 目錄 中收錄了各章節的完整 Go 原始碼範例例如ch.2.x的基礎語法、ch.5.x的資料庫與 NoSQL 存取等這些範例與本小節的測試方法是互補的你可以把本節的gotest.go/gotest_test.go模式套用到任何一個真實的套件上為其核心函式補上單元測試與基準測試。需要強調的是go test不僅僅是開發期工具——每次修改程式碼後執行一次go test就能低成本地完成回歸測試這是維持 Web 專案長期可維護性的基本紀律。小結透過上面對單元測試和壓力測試的學習我們可以看到testing套件非常輕量編寫單元測試和壓力測試案例非常簡單配合內建的go test命令就可以非常方便地進行測試。這樣在我們每次修改完程式碼執行一下go test就可以簡單地完成回歸測試。延伸閱讀目錄上一節使用 GDB 除錯下一節小結赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐ctf-wiki 反調試指南Windows NtGlobalFlag 檢測原理、實戰代碼與繞過方法ctf wiki 反調試指南Windows NtGlobalFlag 檢測原理、實戰代碼與繞過方法 本篇文章整理自 ctf wiki 逆向工程 Windows文档网络安全教程Coil與Kotlin Coroutines異步測試圖像加載測試技巧Coil與Kotlin Coroutines異步測試圖像加載測試技巧 你還在為Android圖像加載的異步測試頭痛嗎當使用Kotlin Coroutines移动开发图像处理缓存抽象如何快速配置Ada语言服务器VSCode和GNAT Studio完整集成指南如何快速配置Ada语言服务器VSCode和GNAT Studio完整集成指南 Ada语言服务器配置是每个Ada开发者提升开发效率的关键一步。作为一门注重安全性文档/教程上一篇如何用Summarize实现视频内容搜索幻灯片文字提取与关键词定位下一篇解锁显卡潜能NVIDIA Profile Inspector完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表