Golang語言是現在炙手可熱的程式語言之一。每個人可能都或多或少的學習和用Golang在寫東西。在程序開發中,測試是一個中必不可少的部分。
有句話"工欲善其事,必先利其器",測試就是開發中值得花功夫"磨快刀"的開發利器。本文我們就一起來學習下如何在Golang測試理念及測試工具。
概述
Golang中附帶了用於編寫和運行測試工具:標準庫testing包以go test命令行工具運行測試套件。和Golang語言本身的設計理念一致,Golang測試理念很簡單:用輕量級的測試包和Golang幫助函數結合實現。在這樣的理念下測試也是代碼。由於Golang開發人員已經知道如何使用抽象和類型編寫Go,因此無需在額外學習特定的測試語法沒個人可能都或多或少和配置。看下面一個簡單的實例,用testing 測試Abs函數簡單代碼:
func TestAbs(t *testing.T) {
got := Abs(-1)
if got != 1 {
t.Errorf("Abs(-1) = %d; want 1", got)
}
}
與上面的代碼對比,下面是使用Ginkgo庫編寫的例子。Ginkg庫為Golang提供了編寫RSpec風格的測試方法:
Describe("Abs", func() {
It("returns correct abs value for -1", func() {
got := Abs(-1)
Expect(got).To(Equal(1))
})
})
兩種表達方式,顯然第一種方法對Golang碼農來說,更加易於理解。RSpec風格中使用諸如函數Describe,Expect的語法模仿了人類語言的表達體驗。但是但是對golang碼農來說,這來的可能會很突兀,需要重新學習和適應。
另一個相對輕量級的庫是testify/assert,它添加了諸如assert.Equal()之類的通用斷言函數。
testify/suite則添加了諸如setup和teardown之類的測試套件實用工具。
"Awesome Go"網站提供了此類第三方軟體包的詳盡列表。
還一個不包含在測試包中的有用的測試工具是reflect.DeepEqual(),這是一個標準庫函數,它用反射來測試深度相等性,即跟隨指針並遞歸到映射,數組等之後的相等性。當測試比較JSON對象或帶有指針的結構之類的東西時,該函數非常有用。以此為基礎的兩個庫是谷歌的go-cmp軟體包和Daniel Nichter的deep,它們類似於DeepEqual,但是格式規範為,更加適應人類閱讀的方式。例如,下面使用go-cmp對MakeUsers()函數進行的測試:
其輸出更加人性化:
內置測試功能
Golang內置的testing包攜帶了各種功能,可記錄信息和報告故障,在運行時跳過測試或僅以簡短模式運行測試。簡短模式提供了一種跳過長時間運行或具有大量設置的測試的方法,這在開發過程中可能會有所幫助。可以用-test.short命令行參數啟用它。
Go的測試運行程序默認情況下按順序執行測試,但提供一個可選的Parallel()函數允許跨多個內核同時運行帶有顯著標記的測試。
在Go 1.14中,測試包添加了Cleanup()函數,該函數註冊了一個在測試完成時要調用的函數。
這是一種簡化拆卸的內置方法,例如,在測試完成後刪除資料庫表:
Go 1.15添加了一個測試助手TempDir(),該助手為當前測試創建(並清理)了一個臨時目錄。
表驅動測試
Golang中常見的習慣用法是在測試各種邊緣情況時避免重複,稱為"表驅動測試"。該技術迭代"切片"中的測試用例,報告每次迭代的失敗:
該t.Errorf()的調用提示存在的問題,但不會停止測試的執行,所以可以報告多個問題。在標準庫測試(例如fmt測試)中,這種表驅動測試的樣式很常見。Golang 1.7引入了一個功能Subtests,它讓運行在命令行中單獨進行部分子測試,可以更好的控制在失敗和平行執行。
模擬和接口
Go的著名語言功能之一是其結構類型化的接口,也被稱為"編譯時鴨子類型 ="。每當需要在運行時改變行為時,接口就很重要,當然其中包括測試。
例如,正如Golang核心開發者Andrew Gerrand在2014年"測試技術"演講的所舉過的例子:文件格式解析器不應像這樣傳遞具體的文件類型:
func Parse(f *os.File) error { ... }
而,Parse()應該僅採用一個僅實現所需功能的小型接口。在這種情況下,無處不在的io.Reader是一個不錯的選擇:
func Parse(r io.Reader) error { ... }
這樣,就可以向解析器提供任何能實現io.Reader的東西,包括文件,字符串緩衝區和網絡連接等。它還使測試變得更加容易(可能使用strings.Reader)。
如果測試僅使用大型接口的一小部分,例如來自多個方法API的一種方法,則可以創建一種新的結構類型,該結構類型嵌入接口以實現API合同,並且僅覆蓋被調用的方法。。
有各種第三方工具,例如GoMock和mockery,可以從接口定義自動生成模仿代碼。
測試用例
Go的軟體包文檔是從原始碼中的注釋生成的。與Javadoc或C#的文檔系統在代碼注釋中大量使用標記不同,Golang的方法是,原始碼中的注釋仍應在原始碼中可讀,而不不是滿篇幅的標記。
它採用與文檔示例類似的方法:下面是可運行的代碼片段,這些片段在運行測試時自動執行,然後包含在生成的文檔中。就像Python的doctests一樣,測試用例將寫入標準輸出,並將輸出和期待的輸出進行比較,避免文檔示例中的回歸。這是一個Abs()函數的測試用例:
func ExampleAbs() {
fmt.Println(Abs(15))
fmt.Println(Abs(-24))
// Output:
// 15
// 24
}
示例函數必須位於*_test.go文件中,並添加Example前綴。當測試運行程序執行時,會自動解析Output:注釋並將其與實際輸出進行比較,如果它們不同,則測試失敗。這些示例作為可運行的Go Playground代碼段包含在生成的文檔中,
基準測試
除測試外,該測試包還允許運行定時基準測試。這些在整個標準庫中大量使用,以確保執行速度不下降。可以使用帶有-bench =選項的go test自動運行基準測試。
例如,下面標準庫的strings.TrimSpace()函數的基準測試用例:
在測試工具將報表中的數字;諸如Benchstat之類的程序可用於比較之前和之後的時間。Benchstat的輸出通常包含在Go的提交消息中,這些消息顯示了性能的提高。
報告顯示,儘管"SomeNonASCII"子測試的速度降低了約9%,但TrimSpace的ASCII快速路徑使純ASCII輸入的速度提高了約5倍。
要診斷某些地方運行緩慢,可以使用內置的profiling性能分析工具,例如運行測試時的-cpuprofile選項。內置的工具pprof以多種格式顯示性能結果,包括火焰圖。
go test命令
社區中對Golang對測試目錄測試用例(在名為*_test.go的文件中)以及測試函數的命名方式(必須有Test前綴)很有意見。但是,go test工具確實一個很好用的工具,它可以確切地知道去哪裡查找並運行測試用例。不需要額外的用於描述測試存放位置makefile或元數據。如果文件和函數是以標準方式命名的,其他的go test就可以自動搞定。go test命令表面很簡單,但是其選項不少也很繁瑣:
go test:在當前目錄中運行測試
go test package:對給定的包運行測試
go test ./...:對當前目錄和所有子包運行測試
go test -run=foo:運行與正則表達式"foo"匹配的測試
go test -cover:運行測試並輸出代碼覆蓋率
go test -bench=.:#同時運行基準
go test -bench=. -cpuprofile cpu.out:運行基準測試,記錄性能分析信息
go test的 -cover模式會生成代碼覆蓋率配置文件,可以使用go tool cover -html = coverage.out將轉為html格式。
總結
Go的測試庫簡單方便,而且可擴展,go test命令行工具進行測試執行,並進行結果處理、基準測試、性能分析和代碼覆蓋率等分析。go testing包中乾貨滿滿,需要我們長時間去探索。當然golang也不排除第三測試模塊,獲取更多的測試體驗,當然你也可以開發適合自己的測試模塊,所有這些在Golang都是可能。