Go 單測從零到溜系列—5-goconvey 的使用
這是 Go 語言單元測試從零到溜系列教程的第 5 篇,介紹瞭如何使用 goconvey 更好地編寫單元測試,讓單元測試結果更直觀、形象。
在上一篇《Go 單測從零到溜系列 4—monkey 打樁測試》中,我們介紹瞭如何在單元測試中使用monkey
對函數和方法進行打樁。
在這一篇中我們將介紹一個人性化的單元測試利器——goconvey。
GoConvey 介紹
GoConvey 是一個非常非常好用的 Go 測試框架,它直接與go test
集成,提供了很多豐富的斷言函數,能夠在終端輸出可讀的彩色測試結果,並且還支持全自動的 Web UI。
安裝
go get github.com/smartystreets/goconvey
使用示例
我們使用goconvey
來爲最開始的基礎示例中的Split
函數編寫單元測試。Split
函數如下:
// split.go
func Split(s, sep string) (result []string) {
result = make([]string, 0, strings.Count(s, sep)+1)
i := strings.Index(s, sep)
for i > -1 {
result = append(result, s[:i])
s = s[i+len(sep):]
i = strings.Index(s, sep)
}
result = append(result, s)
return
}
單元測試文件內容如下:
// split_test.go
import (
"testing"
c "github.com/smartystreets/goconvey/convey" // 別名導入
)
func TestSplit(t *testing.T) {
c.Convey("基礎用例", t, func() {
var (
s = "a:b:c"
sep = ":"
expect = []string{"a", "b", "c"}
)
got := Split(s, sep)
c.So(got, c.ShouldResemble, expect) // 斷言
})
c.Convey("不包含分隔符用例", t, func() {
var (
s = "a:b:c"
sep = "|"
expect = []string{"a:b:c"}
)
got := Split(s, sep)
c.So(got, c.ShouldResemble, expect) // 斷言
})
}
命令行執行單元測試,會在終端輸出可讀性非常好的彩色結果。
goconvey 還支持在單元測試中根據需要嵌套調用,比如:
func TestSplit(t *testing.T) {
// ...
// 只需要在頂層的Convey調用時傳入t
c.Convey("分隔符在開頭或結尾用例", t, func() {
tt := []struct {
name string
s string
sep string
expect []string
}{
{"分隔符在開頭", "*1*2*3", "*", []string{"", "1", "2", "3"}},
{"分隔符在結尾", "1+2+3+", "+", []string{"1", "2", "3", ""}},
}
for _, tc := range tt {
c.Convey(tc.name, func() { // 嵌套調用Convey
got := Split(tc.s, tc.sep)
c.So(got, c.ShouldResemble, tc.expect)
})
}
})
}
這樣輸出最終的測試結果時也會分層級顯示。
斷言方法
GoConvey 爲我們提供了很多種類斷言方法在So()
函數中使用。
一般相等類
So(thing1, ShouldEqual, thing2)
So(thing1, ShouldNotEqual, thing2)
So(thing1, ShouldResemble, thing2) // 用於數組、切片、map和結構體相等
So(thing1, ShouldNotResemble, thing2)
So(thing1, ShouldPointTo, thing2)
So(thing1, ShouldNotPointTo, thing2)
So(thing1, ShouldBeNil)
So(thing1, ShouldNotBeNil)
So(thing1, ShouldBeTrue)
So(thing1, ShouldBeFalse)
So(thing1, ShouldBeZeroValue)
數字數量比較類
So(1, ShouldBeGreaterThan, 0)
So(1, ShouldBeGreaterThanOrEqualTo, 0)
So(1, ShouldBeLessThan, 2)
So(1, ShouldBeLessThanOrEqualTo, 2)
So(1.1, ShouldBeBetween, .8, 1.2)
So(1.1, ShouldNotBeBetween, 2, 3)
So(1.1, ShouldBeBetweenOrEqual, .9, 1.1)
So(1.1, ShouldNotBeBetweenOrEqual, 1000, 2000)
So(1.0, ShouldAlmostEqual, 0.99999999, .0001) // tolerance is optional; default 0.0000000001
So(1.0, ShouldNotAlmostEqual, 0.9, .0001)
包含類
So([]int{2, 4, 6}, ShouldContain, 4)
So([]int{2, 4, 6}, ShouldNotContain, 5)
So(4, ShouldBeIn, ...[]int{2, 4, 6})
So(4, ShouldNotBeIn, ...[]int{1, 3, 5})
So([]int{}, ShouldBeEmpty)
So([]int{1}, ShouldNotBeEmpty)
So(map[string]string{"a": "b"}, ShouldContainKey, "a")
So(map[string]string{"a": "b"}, ShouldNotContainKey, "b")
So(map[string]string{"a": "b"}, ShouldNotBeEmpty)
So(map[string]string{}, ShouldBeEmpty)
So(map[string]string{"a": "b"}, ShouldHaveLength, 1) // supports map, slice, chan, and string
字符串類
So("asdf", ShouldStartWith, "as")
So("asdf", ShouldNotStartWith, "df")
So("asdf", ShouldEndWith, "df")
So("asdf", ShouldNotEndWith, "df")
So("asdf", ShouldContainSubstring, "稍等一下") // optional 'expected occurences' arguments?
So("asdf", ShouldNotContainSubstring, "er")
So("adsf", ShouldBeBlank)
So("asdf", ShouldNotBeBlank)
panic 類
So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "") // or errors.New("something")
So(func(), ShouldNotPanicWith, "") // or errors.New("something")
類型檢查類
So(1, ShouldHaveSameTypeAs, 0)
So(1, ShouldNotHaveSameTypeAs, "asdf")
時間和時間間隔類
So(time.Now(), ShouldHappenBefore, time.Now())
So(time.Now(), ShouldHappenOnOrBefore, time.Now())
So(time.Now(), ShouldHappenAfter, time.Now())
So(time.Now(), ShouldHappenOnOrAfter, time.Now())
So(time.Now(), ShouldHappenBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldNotHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenWithin, duration, time.Now())
So(time.Now(), ShouldNotHappenWithin, duration, time.Now())
自定義斷言方法
如果上面列出來的斷言方法都不能滿足你的需要,那麼你還可以按照下面的格式自定義一個斷言方法。
注意:<>
中的內容是你需要按照實際需求替換的內容。
func should<do-something>(actual interface{}, expected ...interface{}) string {
if <some-important-condition-is-met(actual, expected)> {
return "" // 返回空字符串表示斷言通過
}
return "<一些描述性消息詳細說明斷言失敗的原因...>"
}
WebUI
goconvey 提供全自動的 WebUI,只需要在項目目錄下執行以下命令。
goconvey
默認就會在本機的 8080 端口提供 WebUI 界面,十分清晰地展現當前項目的單元測試數據。
goconvey webui
總結
本文通過一個完整的單元測試示例,介紹瞭如何使用goconvey
工具編寫測試用例、管理測試用例、斷言測試結果,同時也介紹了goconvey
豐富多樣的測試結果輸出形式。
在下一篇將是本系列的最後一篇,我們將重點介紹如何編寫可測試的代碼。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/rp8Xzkmphb4-LBy-8rAjNw