視頻教程和配套博客:goconvey - 課時 1:優雅的單元測試
Go 語言雖然自帶單元測試功能,在 GoConvey 誕生之前也出現了許多第三方輔助庫。但沒有一個輔助庫能夠像 GoConvey 這樣優雅地書寫代碼的單元測試,簡潔的語法和舒適的界面能夠讓一個不愛書寫單元測試的開發人員從此愛上單元測試。
下載安裝 GoConvey:
go get github.com/smartystreets/goconvey
-
在$GOPATH/src目錄下新增了github.com子目錄,該子目錄里包含了GoConvey框架的庫代碼
-
在$GOPATH/bin目錄下新增了GoConvey框架的可執行程序goconvey
API 文檔
請移步 Go Walker。
基本使用方法
示例:
下面是一個能夠實現整數基本四則運算(加、減、乘、除)的代碼:
package main import "errors" func Add(a, b int) int { return a + b } func Subtract(a, b int) int { return a - b } func Multiply(a, b int) int { return a * b } func Division(a, b int) (int, error) { if b == 0 { return 0, errors.New("被除數不能為 0") } return a / b, nil } func main() { }
在上面的代碼中,我們實現了 4 個函數,因此需要為這 4 個函數分別書寫單元測試:
package main import ( . "github.com/smartystreets/goconvey/convey" "testing" ) func TestAdd(t *testing.T) { Convey("將兩數相加", t, func() { So(Add(1, 2), ShouldEqual, 3) }) } func TestSubtract(t *testing.T) { Convey("將兩數相減", t, func() { So(Subtract(1, 2), ShouldEqual, -1) }) } func TestMultiply(t *testing.T) { Convey("將兩數相乘", t, func() { So(Multiply(3, 2), ShouldEqual, 6) }) } func TestDivision(t *testing.T) { Convey("將兩數相除", t, func() { Convey("除以非 0 數", func() { num, err := Division(10, 2) So(err, ShouldBeNil) So(num, ShouldEqual, 5) }) Convey("除以 0", func() { _, err := Division(10, 0) So(err, ShouldNotBeNil) }) }) }
首先,您需要使用官方推薦的方式導入 GoConvey 的輔助包以減少冗余的代碼:. "github.com/smartystreets/goconvey/convey"。
每個單元測試的名稱需要以 Test 開頭,例如:TestAdd,並需要接受一個類型為 *testing.T 的參數。
使用 GoConvey 書寫單元測試,每個測試用例需要使用 Convey 函數包裹起來。它接受的第一個參數為 string 類型的描述;第二個參數一般為 *testing.T,即本例中的變量 t;第三個參數為不接收任何參數也不返回任何值的函數(習慣以閉包的形式書寫)。
Convey 語句同樣可以無限嵌套,以體現各個測試用例之間的關系,例如 TestDivision 函數就采用了嵌套的方式體現它們之間的關系。需要注意的是,只有最外層的 Convey 需要傳入變量 t,內層的嵌套均不需要傳入。
最后,需要使用 So 語句來對條件進行判斷。在本例中,我們只使用了 3 個不同類型的條件判斷:ShouldBeNil、ShouldEqual 和 ShouldNotBeNil,分別表示值應該為 nil、值應該相等和值不應該為 nil。有關詳細的條件列表,可以參見 官方文檔。
測試:
go test -v
輸出結果(ubuntu):
=== RUN TestAdd 將兩數相加 ✔ 1 total assertion --- PASS: TestAdd (0.00s) === RUN TestSubtract 將兩數相減 ✔ 2 total assertions --- PASS: TestSubtract (0.00s) === RUN TestMultiply 將兩數相乘 ✔ 3 total assertions --- PASS: TestMultiply (0.00s) === RUN TestDivision 將兩數相除 除以非 0 數 ✔✔ 除以 0 ✔ 6 total assertions --- PASS: TestDivision (0.00s) PASS ok tttt 0.004s
我們可以看到,輸出結果調理非常清晰,單元測試的代碼寫起來也非常優雅。那么,這就是全部嗎?當然不是。GoConvey 不僅支持在命令行進行人工調用調試命令,還有非常舒適的 Web 界面提供給開發者來進行自動化的編譯測試工作。
Web 界面
想要使用 GoConvey 的 Web 界面特性,需要在相應目錄下執行 goconvey(需使用 go get 安裝到 $GOPATH/bin 目錄下),然后打開瀏覽器,訪問 http://localhost:8080 ,就可以看到下以下界面:
//一般都是在項目根目錄下(具體根據自己的情況) goconvey // 確保 $GOPATH/bin 目錄下存在 goconvey文件


在 Web 界面中,您可以設置界面主題,查看完整的測試結果,使用瀏覽器提醒等實用功能。
其它功能:
- 自動檢測代碼變動並編譯測試
- 半自動化書寫測試用例:http://localhost:8080/composer.html
- 查看測試覆蓋率:http://localhost:8080/reports/
- 臨時屏蔽某個包的編譯測試
Skip
針對想忽略但又不想刪掉或注釋掉某些斷言操作,GoConvey提供了Convey/So的Skip方法:
- SkipConvey函數表明相應的閉包函數將不被執行
- SkipSo函數表明相應的斷言將不被執行
當存在SkipConvey或SkipSo時,測試日志中會顯式打上"skipped"形式的標記:
- 當測試代碼中存在SkipConvey時,相應閉包函數中不管是否為SkipSo,都將被忽略,測試日志中對應的符號僅為一個"⚠"
- 當測試代碼Convey語句中存在SkipSo時,測試日志中每個So對應一個"✔"或"✘",每個SkipSo對應一個"⚠",按實際順序排列
- 不管存在SkipConvey還是SkipSo時,測試日志中都有字符串"{n} total assertions (one or more sections skipped)",其中{n}表示測試中實際已運行的斷言語句數
定制斷言函數
我們先看一下So的函數原型:func So(actual interface{}, assert assertion, expected ...interface{})
第二個參數為assertion,它的原型為:
type assertion func(actual interface{}, expected ...interface{}) string
當assertion的返回值為""時表示斷言成功,否則表示失敗,GoConvey框架中的相關代碼為:
const ( success = "" needExactValues = "This assertion requires exactly %d comparison values (you provided %d)." needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0)." )
我們簡單實現一個assertion函數:
func ShouldGt(actual interface{}, expected ...interface{}) string { if len(expected) == 0{ return "缺少參數 expected..." } val1 := actual.(int) val2 := expected[0].(int) if val1 <= val2{ return fmt.Sprintf("%d 不大於 %d", actual, expected[0]) } return "" }
寫一個簡單的測試:
func TestGt(t *testing.T) { time.Sleep(time.Second*3) //僅僅是測試耗時 Convey("兩數相乘是否大於100", t, func() { So(88, ShouldGt, 100) So(110, ShouldGt, 100) }) }
根據ShouldGt的實現,Convey語句中第一個So將斷言成功,第二個So將斷言失敗。
我們運行測試,查看執行結果,符合期望:
=== RUN TestGt 兩數相乘是否大於100 ✘ Failures: * /home/zhou/go/src/tttt/zhou_test.go Line 59: 88 不大於 100 1 total assertion --- FAIL: TestGt (3.00s) FAIL exit status 1 FAIL tttt 3.002s
要點歸納總結:
- import goconvey包時,前面加點號".",以減少冗余的代碼
- 測試函數的名字必須以Test開頭,而且參數類型必須為*testing.T
- 每個測試用例必須使用Convey函數包裹起來,推薦使用Convey語句的嵌套,即一個函數有一個測試函數,測試函數中嵌套兩級Convey語句,第一級Convey語句對應測試函數,第二級Convey語句對應測試用例
- Convey語句的第三個參數習慣以閉包的形式實現,在閉包中通過So語句完成斷言
- 使用GoConvey框架的 Web 界面特性,作為命令行的補充
- 在適當的場景下使用SkipConvey函數或SkipSo函數
- 當測試中有需要時,可以定制斷言函數
轉載:傳送門
轉載更好的博客:傳送門
