Go 語言中自帶了測試框架,在不引入外部包的情況下,也可以編寫完整的測試职辅。這篇文章來看一下Go 提供原生測試能力,及其不足之處聂示,以及補充這些不足的方法域携。
1. 基本測試框架
在 Go 語言中,所有的測試都需要以 _test.go
結(jié)尾鱼喉,這樣go build 不會去編譯 _test.go
結(jié)尾的文件秀鞭,而 go test 會去編譯 _test.go
結(jié)尾的文件。
在編寫測試的時候蒲凶,我們都會用到 testing
這個包气筋,在這個包中拆内,常用的類型有下面這些:
- testing.T
- testing.B
- testing.M
testing.TB 和 testing.PB 平時用的不多旋圆,在這里就不展開說,感興趣的可以自行去搜索麸恍。上面的三類代表了三種不同的測試灵巧,分別是單元測試搀矫、基準測試和 TestMain 測試,對于不同的測試刻肄,在測試方法的入?yún)⒅腥壳颍仨殠线@個類型。
有一類測試例外敏弃,那就是 Example 測試卦羡,這個測試主要用來在文檔中輸出一些測試案例,Example 測試必須以 Example 開頭麦到,方法不需要任何參數(shù)绿饵,同時要指明這個實例的輸出,像下面這樣:
func ExampleTest() {
fmt.Println("run example test")
// Output:
// run example test
}
所有的測試都可以通過 go test
來發(fā)起瓶颠,例如拟赊,在當前包下發(fā)起測試 go test -v ./
,-v 參數(shù)表示打印測試的過程粹淋,會把測試過程中的標準輸出都打印出來吸祟。
2. 單元測試
單元測試的編寫需要按照一定的規(guī)則來,所有的單元測試都需要以 Test 開頭桃移,后面加上測試的方法名稱屋匕,就像下面這樣:
import (
"fmt"
"testing"
)
func TestDemo(t *testing.T) {
fmt.Println(" run test demo")
}
這就是一個最簡單的單元測試,在實際使用中借杰,一組測試可能會有多個單元測試炒瘟,而且要同時運行,這時我們就需要一個方法將這些測試串聯(lián)起來第步,那就需要用到 TestMain 了疮装,這個方法名稱和簽名是統(tǒng)一的,只能是下面的寫法:
func TestMain(m *testing.M) {
fmt.Println("begin test")
m.Run()
fmt.Println("end test")
}
TestMain(m *testing.M) 在一個包下只能有一個粘都,測試執(zhí)行的時候廓推,會先執(zhí)行這個方法,然后再去執(zhí)行這個包下的所有 Test 測試和 Example 測試翩隧,基準測試則不會執(zhí)行樊展。
而且這個方法可以用來初始化和回收資源,有些測試在運行之前需要初始化一些配置堆生,連接數(shù)據(jù)庫专缠、釋放數(shù)據(jù)庫連接等操作,就可以在這個測試中完成淑仆。
寫完上面的測試之后涝婉,就可以運行測試了,這樣會從 TestMain 開始蔗怠,運行所有的 Test 和 Example 測試:
$ go test -v ./
但有時候我們也會關(guān)心單元測試的覆蓋率墩弯,只要加上一個參數(shù)就可以看到測試的覆蓋率:
$ go test -cover -v ./
3. 基準測試
基準測試通常用來測試某個程序的性能吩跋,基準測試必須要用 Benchmark 開頭,同時方法的入?yún)⒈仨毷?testing.B渔工,就像下面這樣:
func BenchmarkDemo(b *testing.B) {
fmt.Println("run benchmark demo")
}
為了更好的說明基準測試的功能锌钮,我用之前測試字符串拼接的基準測試的例子來說明:
func BenchmarkPlus(b *testing.B) {
str := "this is just a string"
for i := 0; i < b.N; i++ {
stringPlus(str)
}
}
func stringPlus(str string) string {
s := ""
for i := 0; i < 10000; i++ {
s += str
}
return s
}
其中 b.N 不是一個固定的值,這個值的大小由框架自己來決定引矩,上面這側(cè)測試的內(nèi)容是對于一個要拼接一萬次字符傳的函數(shù)進行性能測試梁丘,至于這個測試運行多少次,由框架自己決定旺韭。
運行基準測試的命令如下:
$ go test -bench=. -benchmem .
輸出結(jié)果如下:
goos: darwin
goarch: amd64
pkg: zxin.com/zx-demo/string_benchmark
**BenchmarkPlus-12 12 96586447 ns/op 1086401355 B/op 10057 allocs/op**
PASS
ok zxin.com/zx-demo/string_benchmark 6.186s
加粗的那行是基準測試的輸出兰吟,每列信息的具體含義如下:
- 第一列表示基準測試的方法名稱和所用的 GOMAXPROCS 的值
- 第二列表示這次測試循環(huán)的次數(shù)
- 第三列表示平均每次測試所用的時間,單位為納秒
- 第四列表示平均每次運行所分配的內(nèi)存
- 第五列表示每次運行所分配內(nèi)存的次數(shù)
4. 測試加強
但原生的測試包不夠完美茂翔,比如在單元測試中混蔼,就缺少斷言機制,使得在判斷測試結(jié)果的時候珊燎,非常不方便惭嚣,有一個外部的包可以幫助完善測試的功能。
安裝也很方便:
$ go get github.com/stretchr/testify
這個包從三個方面擴展了 Go 原生測試框架的能力:
- 斷言
原生測試框架里面缺失斷言功能悔政,在很多場景下都不方便晚吞,testify 提供的斷言功能開箱即用,與原生測試框架完美契合:
func TestAssert(t *testing.T) {
assert := assert.New(t)
assert.Equal(123, 123, "they should be equal")
assert.NotEqual(123, 456, "they should not be equal")
o := make(map[string]string)
o["ray"] = "jun"
if assert.NotNil(o) {
assert.Equal("jun", o["ray"])
} else {
assert.Nil(o)
}
}
- Mock 能力
testify 提供了Mock 的能力谋国,可以很好的模擬測試需要的數(shù)據(jù)槽地,對于一些需要復(fù)雜數(shù)據(jù)的測試很有幫助:
type MyMockedObject struct{
mock.Mock
}
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
}
func TestSomething(t *testing.T) {
testObj := new(MyMockedObject)
testObj.On("DoSomething", 123).Return(true, nil)
testMockObj(testObj)
testObj.AssertExpectations(t)
}
func testMockObj(mcObj *MyMockedObject) {
fmt.Println(mcObj.DoSomething(123))
}
- 構(gòu)建更完善的測試
即使有了 TestMain 來初始化配置,但也還是不夠靈活芦瘾,比如在一個包下捌蚊,我需要包含多組測試,而且每組測試的初始化都不一樣近弟,而 testify 提供的 suite 包提供了更加面向?qū)ο蟮臏y試方式缅糟,并且也提供了 setup/teardown 等方法來初始化和回收資源,可以直接使用 go test
進行測試祷愉,不會對現(xiàn)有的測試框架有侵入性修改:
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}
func (suite *ExampleTestSuite) SetupTest() {
fmt.Println("run setup method")
suite.VariableThatShouldStartAtFive = 5
}
func (suite *ExampleTestSuite) TearDownTest() {
fmt.Println("run tear down method")
suite.VariableThatShouldStartAtFive = 0
}
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}
5. 小結(jié)
雖然 Go 原生測試框架已經(jīng)支持編寫很復(fù)雜的測試窗宦,但很多場景下還不是很方便,這時候就有必要引入新的測試加強包 testify二鳄,這個包基本做到了開箱即用赴涵,而且不會破壞現(xiàn)有的測試流程。
文 / Rayjun