Go 測試套件


Go 本身附帶了 testing 套件,搭配 go test 指令,可以自動對套件中的程式碼進行測試,在套件中,測試程式碼必須是 _test.go 結尾,一個套件中可以有多個 _test.go,例如,fmt 套件的原始碼 中,可以看到 export_test.go、fmt_test.go 等,就是測試程式碼。

功能測試

想要使用 Go 的 testing 套件撰寫測試程式碼,必須 import "testing",在 _test.go 中撰寫形式 func TestXxx(t *testing.T) 的函式,Xxx 可以是任意名稱,例如,在 src/mymath 目錄中,寫個 basic_test.go:

package mymath

import "testing"

func TestSomething(t *testing.T) {
    // write some test
}

接著只要執行 go test mymath,就會自動尋找 mymath 套件中的 _test.go 中 Test 開頭的函式並執行,由於目前沒撰寫任何測試內容,測試是以 PASS 結束。

Go 測試套件

如果函式中使用了 testingErrorFail 等與失敗相關的方法,那麼測試就會失敗,例如:

package mymath

import "testing"

func TestSomething(t *testing.T) {
    t.Fail()
}

Go 測試套件

如果想要在測試失敗時,留下一些訊息,可以使用 Error 方法,例如:

package mymath

import "testing"

func TestSomething(t *testing.T) {
    t.Error("something wrong")
}

Go 測試套件

來實際寫個測試,例如,測試一個 Add 函式:

package mymath

import "testing"

func TestAdd(t *testing.T) {
    if Add(1, 2) == 3 {
        t.Log("mymath.Add PASS")
    } else {
        t.Error("mymath.Add FAIL")
    }
}

由於目前還沒有撰寫 Add 函式,因此若執行 go test mymath 的話,會以 [build failed] 收場,如果在 basic.go 撰寫了正確的 Add 函式:

package mymath

func Add(a, b int) int {
    return a + b
}

不過,如果直接執行 go test mymath 的話,只會顯示 ok 等字眼,不會顯示 Log 的訊息,想看到 Log 的訊息的話,必須加上 -v 引數(代表 verbose),例如:

Go 測試套件

如果 Log 之後接上 Fail 函式,那麼不加上 -v,也會顯示 Log 的訊息,實際上,Error 函式就是相當於先以 Log 顯示指定的訊息,然後再接上 Fail 函式。

如果想要略過測試,那麼可以使用 Skip 函式,例如:

package mymath

import "testing"

func TestSomething(t *testing.T) {
    t.Skip()
}

func TestAdd(t *testing.T) {
    if Add(1, 2) == 3 {
        t.Log("mymath.Add PASS")
    } else {
        t.Error("mymath.Add FAIL")
    }
}

TestSomething 中如果沒有執行 Skip 會是兩個 PASS 的測試結果,若如上執行了 Skip,會是一個 SKIP 與一個 PASS 的測試結果。例如:

Go 測試套件

如果你想指定某個測試,可以使用 -run 引數,這接受一個正則表示式,例如,若只想執行 TestAdd,那麼可以如下:

Go 測試套件

效能評測

如果想進行效能評測(Benchmark),那麼 _test.go 中,評測函式必須是 func BenchmarkXxx(b *testing.B) 形式,例如:

package mymath

import "testing"

func TestSomething(t *testing.T) {
    t.Skip()
}

func TestAdd(t *testing.T) {
    if Add(1, 2) == 3 {
        t.Log("mymath.Add PASS")
    } else {
        t.Error("mymath.Add FAIL")
    }
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

為了進行評測,被測試的函式要執行多次,以求得每次執行的平均時間,要執行多次函式可以使用迴圈,並以 b.N 作為邊界,b.N 目標預設是 1000000000,評測預設會在一秒內,以越來越大的 b.N 執行迴圈,這是為了讓評測進入穩定狀態,以收集到可靠的評測資料;如果運行時間到了,b.N 目標值仍未達成,就以現有收集到的資料來回報評測結果。

你可以在執行 go test 時,加上 -bench 引數,這個引數後可以使用正則表示式,來指定符合的評測函式名稱,例如,想執行所有評測函式,可以使用 -bench="."

Go 測試套件

評測的結果中顯示,達到了 b.N 預設目標 100000000 次,平均每次迴圈花了 0.58 奈秒(nanosecond)。

如果只想進行效能評測,可以使用 -run 引數,這本來是用來指定要執行的測試函式,只要指定一個不符合任何測試函式的正則表示式,就可以略過所有測試,只執行評測函式了,例如:

Go 測試套件

方才談到,評測預設的運行時間是一秒,如果在這個時間內,無法達到 b.N 的目標值,可以增加這個時間,這要使用 -benchtime 引數,指定的格式像是 1h30s,例如:

Go 測試套件

如果想固定 b.N 的值,Go 1.12 以後可以使用 x 後置,例如指定執行 100000000000 次(預設 b.N 目標的 10 倍)並收集結果:

Go 測試套件

-count 可以指定評測重啟幾次:

Go 測試套件

想知道更多 Go 測試套件的細節,可以參考 Package testing 的說明。