go語言測試框架

go語言內置的測試框架能夠完成基本的功能測試,基準測試,和樣本測試。

測試框架

  • go語言測試單元以包為單位組織,包含包里的一個或者多個測試文件。
  • 測試文件以_test.go結尾,通常放在待測代碼相同目錄下,即他們屬于同一個包。
  • 測試用例以TestXxxx/ExampleXxxx/BenchmarkXxxx的格式組織在測試文件里。
    當然也可以在測試文件里面定義其他非上述格式的本地函數,以供調用。

功能測試: TestXxxx(t *testing.T)

功能測試偏向于測試源碼文件中的具體功能,屬于UT的范疇,比如具體的函數功能。

先舉一個例子來說
假定定義了一個函數計算平均值:

$ cat myavg.go
package myavg

func MyAvg(a []float64) float64 {
  ...
}

下面來為這個函數增加測試用例

$ cat myavg_test.go
package myavg

import (
    "testing"
)

func TestMyAvg(t *testing.T) {
    a := []float64 {1, 2, 3, 4, 5}
    e := 3
    v := MyAvg(a)
    if v != 3 {
        t.Errorf("case (%v) expect (%v) but got (%v)", a, e, v)
    }
}

運行

$ go test -v
=== RUN   TestMyAvg
--- PASS: TestMyAvg (0.00s)
PASS
ok      myavg   0.008s

幾點說明:

  • 測試文件是否與待測文件在相同目錄下
    這并不是固定的;如果放在同一個目錄下使用相同的包(package)名,方便當代碼功能更改時,及時相應的更改測試代碼;如果把測試文件單獨組織在另一個包下面,例如myavg_test,也可以完成測試功能,例如:
$ cat myavg_test.go
package myavg_test

import (
    "myavg"

    "testing"
)

func TestMyAvg(t *testing.T) {
    a := []float64 {1, 2, 3, 4, 5}
    e := 3
    v := myavg.MyAvg(a)
    if v != 3 {
        t.Errorf("case (%v) expect (%v) but got (%v)", a, e, v)
    }
}

這個例子中我們把測試文件放在一個單獨的包(myavg_test)里,區別就是必須import myavg包,然后調用MyAvg()的時候必須指定包名myavg,因為他們屬于不同的包(package)了。

其實go只規定了一點,即以_test.go結尾的文件會認為是測試文件,在編譯(go build)的時候會被忽略,只在測試(go test)的時候被使用,所以測試文件是否和待測文件放在相同包里還是不同包里,可以根據項目,或者使用習慣組織。

  • 測試用例名稱TestXxxx
    注意第一個字母X不能是小寫字母,在實際項目中,第一個字母一般是大寫字母或者下劃線(_),這樣便于代碼的閱讀;其實也可以是數字,但這實在是可讀性差點了,雖然語法上沒有問題。例如
TestMyAvg   // OK
Test_MyAvg  // OK
Test_myAvg  // OK
Test1myAvg  // not suggested
TestmyAvg   // Fail

測試例子的改進
前面的測試例子TestMyAvg中,我們給了一個用例,這個例子我們可以進行適當改進,方便多個組合的測試。

package myavg

import (
    "testing"
)

type casepair struct {
    val []float64
    avg float64
}

func TestAverage2(t *testing.T) {
    var cases = []casepair {
        { []float64{1, 2},              1.5 },
        { []float64{1, 1, 1, 1, 1, 1},  1 },
        { []float64{1, -1},             0 },
    }

    for _, pair := range cases {
        v := MyAvg(pair.val)
        if v != pair.avg {
            t.Errorf("case (%v) expect (%v) but got (%v)", pair.val, pair.avg, v)
        }
    }
}

運行

$ go test -v
=== RUN   TestAverage2
--- PASS: TestAverage2 (0.00s)
PASS
ok      myavg   0.003s

以這種方式組織測試材料,在定義輸入的時候就定義好了結果輸出,便于測試用例的組織,以及將來的添加,刪除,等修改維護。

樣本測試: ExampleXxxx()

樣本測試用來驗證運行的輸出內容是否與預期的一樣。
樣本測試的格式和功能測試的格式類似,只是例子以ExampleXxxx格式,然后在函數中以//Output的方式指明輸出,例如

package myavg

import (
    "fmt"
)

func ExampleHello() {
    a := []float64 {1, 2, 3, 4, 5}
    v := MyAvg(a)
    fmt.Printf("%.1f", v)
    // Output: 3.0
}

type casepair struct {
    val []float64
    avg float64
}

func ExampleHello2() {
    var cases = []casepair {
        { []float64{1, 2},              1.5 },
        { []float64{1, 1, 1, 1, 1, 1},  1 },
        { []float64{1, -1},             0 },
    }

    for _, pair := range cases {
        v := MyAvg(pair.val)
        fmt.Printf("%.1f\n", v)
    }

    // Output:
    // 1.5
    // 1.0
    // 0.0
}

寫在注釋后面的Output定義了函數執行希望的輸出內容,go測試框架會比較這些輸出和實際的輸出是否一致,來決定測試用例是否通過。另外樣本測試并不需要import testing包,而只需要定義函數名格式為ExampleXxxx即可,testing包是功能測試必須的。
定義Output的格式比較復雜可以參考go語言文檔,這里不細說了,例子只給出了最基本的Output用法。

其實仔細想想,功能測試和樣本測試沒啥區別,他們可以互相改造,只是內容比較,用戶可以以程序的方式比較字符串內容,或者由測試框架來比較而已。所以實際場景下樣本測試使用的并不多。
運行:

$ go test -v
=== RUN   ExampleHello
--- PASS: ExampleHello (0.00s)
=== RUN   ExampleHello2
--- PASS: ExampleHello2 (0.00s)
PASS
ok      myavg   0.003s

我們給一個沒有通過反例看一下失敗的場景是怎么樣的:

package myavg

import (
    "fmt"
)

func ExampleHello3() {
    fmt.Println("Hello")
    // Output:
    // hello
}

$ go test -v
=== RUN   ExampleHello
--- FAIL: ExampleHello3 (0.00s)
got:
Hello
want:
hello
FAIL
exit status 1
FAIL    myavg   0.004s

測試用例里面希望的輸出是hello,而實際的執行輸出是Hello,第一個字母的大小寫不一致,所有這個用例運行失敗。

基準測試: BenchmarkXxxx(b *testing.B)

沒有弄過,以后再更新。

測試Main函數: TestMain(m *testing.M)

測試的TestMain函數主要用來在運行測試用例之前做一些必要的setup以及之后的tearDown操作。注意是在所有的測試運行之前和之后,因此一個測試包里面只能定義一個TestMain函數,下面的例子:

package myavg

import (
    "os"
    "log"
    "flag"

    "testing"
)


var maxValue  *int    = flag.Int   ("max",   100,      "the maxinum buffer size")
var typeValue *string = flag.String("type", "average", "the default type value")

func TestMain(m *testing.M) {
    flag.Parse()
    log.Printf("option[max]=(%d)\n", *maxValue)
    log.Printf("option[type]=(%s)\n", *typeValue)

    setup(*maxValue, *typeValue)
    exitcode := m.Run() // run all cases
    tearDown()
    os.Exit(exitcode)
}


func setup(maxValue int, typeValue string) {
    log.Println("Entry of setup")
}

func tearDown() {
    log.Println("Exit of tearDown")
}

運行

$ go test -v -max 12 -type anytype
2017/11/03 21:59:57 option[max]=(12)
2017/11/03 21:59:57 option[type]=(anytype)
2017/11/03 21:59:57 Entry of setup
...
2017/11/03 21:59:57 Exit of tearDown
ok      myavg   0.004s

首先在TestMain分析命令行參數,我們定義了max和type兩個參數,然后把參數傳個setup()做必要的初始化,接著運行測試案例,最后退出之前調用tearDown()做必要的清除操作。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容