Go的測試框架

Go 語言中自帶了測試框架,在不引入外部包的情況下,也可以編寫完整的測試。這篇文章來看一下Go 提供原生測試能力,及其不足之處,以及補(bǔ)充這些不足的方法。

1. 基本測試框架

在 Go 語言中,所有的測試都需要以 _test.go 結(jié)尾,這樣go build 不會去編譯 _test.go 結(jié)尾的文件,而 go test 會去編譯 _test.go 結(jié)尾的文件。

在編寫測試的時(shí)候,我們都會用到 testing 這個(gè)包,在這個(gè)包中,常用的類型有下面這些:

  • testing.T
  • testing.B
  • testing.M

testing.TB 和 testing.PB 平時(shí)用的不多,在這里就不展開說,感興趣的可以自行去搜索。上面的三類代表了三種不同的測試,分別是單元測試、基準(zhǔn)測試和 TestMain 測試,對于不同的測試,在測試方法的入?yún)⒅?,必須帶上這個(gè)類型。

有一類測試?yán)?,那就?Example 測試,這個(gè)測試主要用來在文檔中輸出一些測試案例,Example 測試必須以 Example 開頭,方法不需要任何參數(shù),同時(shí)要指明這個(gè)實(shí)例的輸出,像下面這樣:

func ExampleTest() {
    fmt.Println("run example test")
    // Output:
    // run example test
}

所有的測試都可以通過 go test 來發(fā)起,例如,在當(dāng)前包下發(fā)起測試 go test -v ./ ,-v 參數(shù)表示打印測試的過程,會把測試過程中的標(biāo)準(zhǔn)輸出都打印出來。

2. 單元測試

單元測試的編寫需要按照一定的規(guī)則來,所有的單元測試都需要以 Test 開頭,后面加上測試的方法名稱,就像下面這樣:

import (
    "fmt"
    "testing"
)

func TestDemo(t *testing.T) {
    fmt.Println(" run test demo")
}

這就是一個(gè)最簡單的單元測試,在實(shí)際使用中,一組測試可能會有多個(gè)單元測試,而且要同時(shí)運(yùn)行,這時(shí)我們就需要一個(gè)方法將這些測試串聯(lián)起來,那就需要用到 TestMain 了,這個(gè)方法名稱和簽名是統(tǒng)一的,只能是下面的寫法:

func TestMain(m *testing.M)  {
    fmt.Println("begin test")
    m.Run()
    fmt.Println("end test")
}

TestMain(m *testing.M) 在一個(gè)包下只能有一個(gè),測試執(zhí)行的時(shí)候,會先執(zhí)行這個(gè)方法,然后再去執(zhí)行這個(gè)包下的所有 Test 測試和 Example 測試,基準(zhǔn)測試則不會執(zhí)行。

而且這個(gè)方法可以用來初始化和回收資源,有些測試在運(yùn)行之前需要初始化一些配置,連接數(shù)據(jù)庫、釋放數(shù)據(jù)庫連接等操作,就可以在這個(gè)測試中完成。

寫完上面的測試之后,就可以運(yùn)行測試了,這樣會從 TestMain 開始,運(yùn)行所有的 Test 和 Example 測試:

$ go test -v ./

但有時(shí)候我們也會關(guān)心單元測試的覆蓋率,只要加上一個(gè)參數(shù)就可以看到測試的覆蓋率:

$ go test -cover -v ./

3. 基準(zhǔn)測試

基準(zhǔn)測試通常用來測試某個(gè)程序的性能,基準(zhǔn)測試必須要用 Benchmark 開頭,同時(shí)方法的入?yún)⒈仨毷?testing.B,就像下面這樣:

func BenchmarkDemo(b *testing.B) {
    fmt.Println("run benchmark demo")
}

為了更好的說明基準(zhǔn)測試的功能,我用之前測試字符串拼接的基準(zhǔn)測試的例子來說明:

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 不是一個(gè)固定的值,這個(gè)值的大小由框架自己來決定,上面這側(cè)測試的內(nèi)容是對于一個(gè)要拼接一萬次字符傳的函數(shù)進(jìn)行性能測試,至于這個(gè)測試運(yùn)行多少次,由框架自己決定。

運(yùn)行基準(zhǔn)測試的命令如下:

$ 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

加粗的那行是基準(zhǔn)測試的輸出,每列信息的具體含義如下:

  • 第一列表示基準(zhǔn)測試的方法名稱和所用的 GOMAXPROCS 的值
  • 第二列表示這次測試循環(huán)的次數(shù)
  • 第三列表示平均每次測試所用的時(shí)間,單位為納秒
  • 第四列表示平均每次運(yùn)行所分配的內(nèi)存
  • 第五列表示每次運(yùn)行所分配內(nèi)存的次數(shù)

4. 測試加強(qiáng)

但原生的測試包不夠完美,比如在單元測試中,就缺少斷言機(jī)制,使得在判斷測試結(jié)果的時(shí)候,非常不方便,有一個(gè)外部的包可以幫助完善測試的功能。

安裝也很方便:

$ go get github.com/stretchr/testify

這個(gè)包從三個(gè)方面擴(kuò)展了 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 來初始化配置,但也還是不夠靈活,比如在一個(gè)包下,我需要包含多組測試,而且每組測試的初始化都不一樣,而 testify 提供的 suite 包提供了更加面向?qū)ο蟮臏y試方式,并且也提供了 setup/teardown 等方法來初始化和回收資源,可以直接使用 go test 進(jìn)行測試,不會對現(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ù)雜的測試,但很多場景下還不是很方便,這時(shí)候就有必要引入新的測試加強(qiáng)包 testify,這個(gè)包基本做到了開箱即用,而且不會破壞現(xiàn)有的測試流程。

文 / Rayjun

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 參考《Go語言圣經(jīng)》P395 一、概述 go test命令是一個(gè)按照一定的約定和組織的測試代碼的驅(qū)動程序。在包目錄...
    合肥黑閱讀 940評論 0 4
  • go test命令,相信大家都不陌生,常見的情況會使用這個(gè)命令做單測試、基準(zhǔn)測試和http測試。go test還是...
    筆名輝哥閱讀 2,884評論 0 52
  • 在*_test.go文件中,有三種類型的函數(shù):測試函數(shù)、基準(zhǔn)測試(benchmark)函數(shù)、示例函數(shù)。一個(gè)測試函數(shù)...
    一斗閱讀 1,719評論 0 0
  • http://c.biancheng.net/view/124.html[http://c.biancheng.n...
    JunChow520閱讀 460評論 0 1
  • go語言內(nèi)置的測試框架能夠完成基本的功能測試,基準(zhǔn)測試,和樣本測試。 測試框架 go語言測試單元以包為單位組織,包...
    CodingCode閱讀 3,082評論 0 0

友情鏈接更多精彩內(nèi)容