文章目錄
go test [flag] flag 有:
go help testflag
查看有那些 flag:
-args
: 測試函數接收命令行參數,注意:該參數後所有參數會被當做命令行參數傳遞給測試用例,正確使用方式舉例:go test -v -run TestFibOnce b_test.go -args "in 7" expected=13
;-c
: 將測試文件編譯生成可執行函數, 加-o
指定文件名(默認文件名test.test
);- 編譯:
go test -c -o test_fib.test b_test.go
; - 使用二進制文件:
./test_fib.test -test.v -test.run TestFibOnce "in=7" expected=13
測試用例同上;T -i
: 安裝作爲測試依賴項的軟件包。不要運行測試。-json
: 以 json 格式輸出。-bench regexp
:僅運行與正則表達式匹配的那些基準。多個正則表達式以/
隔開-benchtime t
: 對每個基準運行足夠的迭代,以t
表示爲time.Duration
(例如-benchtime 1h30s
)。- 默認值爲1秒(1s)。
- 特殊語法
Nx
意味着要運行基準測試N次(例如,-benchtime 100x
)。 -count n
: 運行每個測試和基準測試n
次(默認爲1)。如果設置了-cpu
,則對每個GOMAXPROCS
值運行n次。示例始終運行一次。-cover
: 覆蓋率-covermode set,count,atomic
: 設置要測試的包裝的覆蓋率分析的模式。-cpu
: 指定應爲其執行測試或基準的GOMAXPROCS值的列表。 默認值爲GOMAXPROCS的當前值。-failfast
: 第一次測試失敗後,請勿開始新的測試。-list regexp
: 列出 與正則匹配的測試用例列表,例如go test -list Test .
列出名字以Test
爲開頭的測試用例;-parallel n
: 調用t.Parallel的測試功能。並設置 並行運行的測試數量爲n,n 默認爲GOMAXPROCS
,請注意,-parallel僅適用於單個測試二進制文件。-run regexp
: 執行與正則表達式相匹配的測試用例;-short
: 一個快速測試的標記,在測試用例中可以使用testing.Short()
來繞開一些測試,詳細使用方法看 -short 的使用;-timeout d
: 如果測試用例的運行時間超過持續時間 d,則出現恐慌。如果 d 爲 0,則禁用超時。默認值爲 10分鐘(10m
);-v
: 顯示詳細測試信息,打印t.Log()
和t.Logf()
輸出;-benchmem
: 打印基準測試的內存分配統計信息。-blockprofile block.out
: 性能剖析, 記錄 阻塞事件的分析數據 到block.out
,可以供給go tool pprof
使用。- 例如:
go tool pprof test.test block.out
輸入web
會生成 svg 圖像(需要安裝graphviz
):
-blockProfilerate n
: 探查器每n納秒中採樣一個阻塞事件;-coverprofile cover.out
: 看覆蓋率;-cpuprofile cpu.out
: 性能剖析, 記錄 cpu 性能刨析 到文件,可以供給go tool pprof
使用- 例如:
go tool pprof test.test cpu.out
輸入web
會生成 svg 圖像(需要安裝graphviz
):
-memprofile mem.out
: 性能剖析, 同上 記錄內存使用數據到文件,可以供給go tool pprof
使用- 例如:
go tool pprof test.test mem.out
輸入web
會生成 svg 圖像(需要安裝graphviz
):
打印/報告
- 當我們遇到一個斷言錯誤的時候,標識這個測試失敗,會使用到:
Fail: 測試失敗,測試繼續,也就是之後的代碼依然會執行
FailNow: 測試失敗,測試中斷
在 FailNow 方法實現的內部,是通過調用 runtime.Goexit()
來中斷測試的。
- 當我們遇到一個斷言錯誤,只希望跳過這個錯誤,但是不希望標識測試失敗,會使用到:
SkipNow: 跳過測試,測試中斷
在 SkipNow 方法實現的內部,是通過調用 `runtime.Goexit()` 來中斷測試的。
- 當我們只希望打印信息,會用到 :
Log: 輸出信息
Logf: 輸出格式化的信息
注意:默認情況下,單元測試成功時,它們打印的信息不會輸出,可以通過加上 -v` 選項,輸出這些信息。但對於基準測試,它們總是會被輸出。
- 當我們希望跳過這個測試,並且打印出信息,會用到:
Skip: 相當於 Log + SkipNow
Skipf: 相當於 Logf + SkipNow
- 當我們希望斷言失敗的時候,標識測試失敗,並打印出必要的信息,但是測試繼續,會用到:
Error: 相當於 Log + Fail
Errorf: 相當於 Logf + Fail
- 當我們希望斷言失敗的時候,標識測試失敗,打印出必要的信息,但中斷測試,會用到:
Fatal: 相當於 Log + FailNow
Fatalf: 相當於 Logf + FailNow
T 類型 普通測試用例
T 類型用於管理測試狀態並支持格式化測試日誌。測試日誌會在執行測試的過程中不斷累積,並在測試完成時轉儲至標準輸出。測試用例以 Test
開頭:
// 被測試的函數
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
// 執行測試
func TestFib(t *testing.T) {
var fibTests = []struct {
in int // input
expected int // expected result
}{
{1, 1},
{2, 1},
{3, 2},
{4, 3},
{5, 5},
{6, 8},
{7, 13},
}
for _, tt := range fibTests {
actual := Fib(tt.in)
if actual != tt.expected { // 斷言結果是否和預期相等
t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected)
}
}
}
/* go test -v -run TestFib b_test.go
# 參數說明:
-v 打印詳細信息
-run 運行執行測試用例函數
後跟用例尋找範圍,文件名 或 "."(代表當前目錄下所有文件)
# 輸出:
=== RUN TestFib
--- PASS: TestFib (0.00s)
PASS
ok command-line-arguments 0.002s
*/
除了打印報告的方法外還有:方法:
Name 返回當前測試用例名稱
func (t *T) Name() string
Parallel 標記當前測試用例可以並行測試
func (t *T) Parallel()
比如下面兩個測試用例可並行執行測試:
func TestOne(t *testing.T) {
t.Parallel()
...
}
func TestTwo(t *testing.T) {
t.Parallel()
...
}
Helper 將函數標記爲測試助手函數
func (t *T) Helper()
使用示例:
func failure(t *testing.T) {
t.Helper() // 標記自己爲helper函數
t.Fatal("failure")
}
func TestHelper(t *testing.T){
failure(t)
}
/* 執行 o test -v -run TestHelper 輸出:
~/Projects/go/src/test/test $ go test -v -run TestHelper
=== RUN TestHelper
TestHelper: b_test.go:128: failure // 這裏錯誤信息 顯示是在 第 128 行 即 TestHelper 函數中
--- FAIL: TestHelper (0.00s)
FAIL
exit status 1
FAIL test/test 0.006s
對比註釋掉 t.Helper() 輸出:
~/Projects/go/src/test/test $ go test -v -run TestHelper
=== RUN TestHelper
TestHelper: b_test.go:125: failure // 這裏錯誤信息 顯示是在 第 128 行 即 failure 函數中
--- FAIL: TestHelper (0.00s)
FAIL
exit status 1
FAIL test/test 0.006s
*/
Run 執行 子測試
func (t *T) Run(name string, f func(b *B)) bool
子測試,又叫 命名測試 (named tests),它意味着您現在可以擁有嵌套測試,這對於自定義(和過濾)給定測試的示例非常有用。
使用示例:
func TestFoo(t *testing.T) {
// <setup code>
t.Run("A=1", func(t *testing.T) { ... })
t.Run("A=2", func(t *testing.T) { ... })
t.Run("B=1", func(t *testing.T) { ... })
// <tear-down code>
}
可通過指定 -run regexp
和 -bench regexp
flag 的正則來執行某子測試:
go test -run '' # Run 所有測試。
go test -run Foo # Run 匹配 "Foo" 的頂層測試,例如 "TestFooBar"。
go test -run Foo/A= # 匹配頂層測試 "Foo",運行其匹配 "A=" 的子測試。
go test -run /A=1 # 運行所有匹配 "A=1" 的子測試。
子測試也可以使用 t.Parallel()
來標記並行執行。所有的子測試完成後,父測試纔會完成。在下面👇這個例子中,所有的測試是相互並行運行的,當然也只是彼此之間,不包括定義在其他頂層測試的子測試:
func TestGroupedParallel(t *testing.T) {
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
...
})
}
}
B 類型 基準測試(壓力測試)
B 類型用於管理基準測試的計時行爲,並指示應該迭代地運行測試多少次。B 類型測試用例以 Benchmark
開頭:
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("hello")
}
}
func BenchmarkBenchmem(b *testing.B) {
for n := 0; n < b.N; n++ {
actual := Fib(7)
fmt.Printf("Fib(%d)=%d\n", n, actual)
}
}
B 類型有 T 類型的所有方法,除了和 T 共有的函數外還有:
ReportAllocs 打開當前基準測試的內存統計功能
func (b *B) ReportAllocs()
打開當前基準測試的內存統計功能,與使用 -test.benchmem
設置類似,但 ReportAllocs
隻影響那些調用了該函數的基準測試。
ResetTimer 重置計時器
func (b *B) ResetTimer()
對已經逝去的基準測試時間以及內存分配計數器進行清零。對於正在運行中的計時器,這個方法不會產生任何效果。
使用示例:
func BenchmarkBigLen(b *testing.B) {
big := NewBig() // 費時操作 比如初始化某個變量
b.ResetTimer()
for i := 0; i < b.N; i++ {
big.Len()
}
}
RunParallel 並行執行給定測試
func (b *B) RunParallel(body func(*PB))
以並行的方式執行給定的基準測試。 RunParallel
會創建出多個 goroutine
,並將 b.N
分配給這些 goroutine
執行, 其中 goroutine
數量的默認值爲 GOMAXPROCS
。用戶如果想要增加非 CPU
受限(``non-CPU-bound)基準測試的並行性, 那麼可以在
RunParallel之前調用
SetParallelism。
RunParallel 通常會與
-cpu` 標誌一同使用。
body
函數將在每個 goroutine
中執行,這個函數需要設置所有 goroutine
本地的狀態, 並迭代直到 pb.Next
返回 false 值爲止。因爲 StartTimer
、 StopTimer
和 ResetTimer
這三個函數都帶有全局作用,所以 body
函數不應該調用這些函數;除此之外,body
函數也不應該調用 Run
函數。
func BenchmarkRunParallel(b *testing.B) {
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
// RunParallel 將創建 GOMAXPROCS 個 goroutine,並在其中分配工作。
b.RunParallel(func(pb *testing.PB){
// 每個goroutine都有自己的byte.Buffer。
var buf bytes.Buffer
for pb.Next() {
// 循環體在所有goroutine中總共執行b.N次。
buf.Reset()
templ.Execute(&buf, "World")
}
})
}
// go test -v -bench=^BenchmarkRunParallel$ -run=^$
SetBytes 記錄處理的字節數
func (b *B) SetBytes(n int64)
記錄在單個操作中處理的字節數量。 在調用了這個方法之後, 基準測試將會報告 ns/op
以及 MB/s
.
SetParallelism 開始對測試進行計時
func (b *B) StartTimer()
這個函數在基準測試開始時會自動被調用,它也可以在調用 StopTimer
之後恢復進行計時。
StopTimer 停止對測試進行計時。
func (b *B) StopTimer()
停止對測試進行計時。
測試控制檯輸出的例子
func ExampleHello() {
fmt.Println("Hello")
// Output: hello
}
/* go test -run ExampleHello
# 輸出:
--- FAIL: ExampleHello (0.00s)
got:
Hello
want:
hello
FAIL
FAIL command-line-arguments 0.009s
FAIL
Main 測試
TestMain
的使用場景:
開始測試之前有初始化操作,比如 http 測試有時需要授權操作、創建連接時。
測試結束後要做數據清理等操作時。
func Add(a,b int) int {
return a+b
}
func TestMain(m *testing.M) {
fmt.Println("開始測試...")
m.Run()
fmt.Println("測試結束...")
}
/* 輸出:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
測試結束...
ok command-line-arguments 0.007s
HTTP 測試
Go 語言目前的 web 開發是比較多的,那麼在我們對功能函數有了測試之後,HTTP 的測試又該怎樣做呢?
Go 的標準庫爲我們提供了一個 httptest
的庫,通過它就能夠輕鬆的完成 HTTP 的測試。
示例1:
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
)
var HandleHelloWorld = func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
func main() {
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
HandleHelloWorld(w, req)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(resp.Header.Get("Content-Type"))
fmt.Println(string(body))
}
示例2:
package test
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func testAPI(w http.ResponseWriter, r *http.Request){
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "請求 body 異常", 500)
}
fmt.Println(string(body))
// fmt.Fprint(w, "ok")
http.Error(w, "請求 body 異常", 500)
}
func Test_testApi(t *testing.T) {
tests := []struct {
name string
}{
{
name: "test api",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T){
// 傳入一個 http 處理器 創建一個 server
ts := httptest.NewServer(http.HandlerFunc(testAPI))
defer ts.Close()
params := struct{
Params string `json:"params"`
}{
Params: "params body",
}
paramsByte, _ := json.Marshal(params)
// 像上面那個處理器發送一個 post 請求
resp, err := http.Post(ts.URL, "application/json", bytes.NewBuffer(paramsByte))
if err != nil {
t.Error(err)
}
defer resp.Body.Close()
// 檢查返回 http status
t.Logf("Status Code: %d",resp.StatusCode)
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
t.Error(string(body))
}
})
}
}
/*
=== RUN Test_testApi
=== RUN Test_testApi/test_api
{"params":"params body"}
Test_testApi/test_api: http_test.go:51: Status Code: 500
Test_testApi/test_api: http_test.go:54: 請求 body 異常
--- FAIL: Test_testApi (0.00s)
--- FAIL: Test_testApi/test_api (0.00s)
FAIL
FAIL command-line-arguments 0.021s
FAIL
*/
示例3 beego 框架測試:
b, err := json.Marshal(&Req{Username:"test", Passoword:"123456"})
r, _ := http.NewRequest("POST", "/user/login", bytes.NewBuffer(b))
r.Header.Set("User-Agent", "beego_server")
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
r.ContentLength = int64(len(b))
r.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
更復雜的示例:beego 單元測試示例.md
補充
覆蓋率
由單元測試的代碼,觸發運行到的被測試代碼的代碼行數佔所有代碼行數的比例,被稱爲測試覆蓋率,代碼覆蓋率不一定完全精準,但是可以作爲參考,可以幫我們測量和我們預計的覆蓋率之間的差距。
go tool
工具提供了測量覆蓋率的功能:
# 生成指定 package 的測試覆蓋率(fib.out 後面不帶參數的,默認是命令所在目錄)
go test -v -covermode=count -coverprofile fib.out
# 查看彙總的 fib 測試覆蓋率
go tool cover -func=fib.out
# 生成 html
go tool cover -html=fib.out -o fib.html
使用Short標記可跳過的測試用例
func TestTimeConsuming(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
...
}
運行 go test
時添加 -short
flag 即可跳過如上面代碼的測試用例,go test -short -v -run=.