亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Golang > 正文

一步步教你編寫可測試的Go語言代碼

2020-04-01 19:07:58
字體:
來源:轉載
供稿:網友

第一個測試 “Hello Test!”

首先,在我們$GOPATH/src目錄下創建hello目錄,作為本文涉及到的所有示例代碼的根目錄。

然后,新建名為hello.go的文件,定義一個函數hello() ,功能是返回一個由若干單詞拼接成句子:

package hellofunc hello() string { words := []string{"hello", "func", "in", "package", "hello"} wl := len(words) sentence := "" for key, word := range words {  sentence += word  if key < wl-1 {   sentence += " "  } else {   sentence += "."  } } return sentence}

接著,新建名為hello_test.go的文件,填入如下內容:

package helloimport ( "fmt" "testing")func TestHello(t *testing.T) { got := hello() expect := "hello func in package hello." if got != expect {  t.Errorf("got [%s] expected [%s]", got, expect) }}func BenchmarkHello(b *testing.B) { for i := 0; i < b.N; i++ {  hello() }}func ExampleHello() { hl := hello() fmt.Println(hl) // Output: hello func in package hello.}

最后,打開終端,進入hello目錄,輸入go test命令并回車,可以看到如下輸出:

PASSok  hello 0.007s

編寫測試代碼

Golang的測試代碼位于某個包的源代碼中名稱以_test.go結尾的源文件里,測試代碼包含測試函數、測試輔助代碼和示例函數;測試函數有以Test開頭的功能測試函數和以Benchmark開頭的性能測試函數兩種,測試輔助代碼是為測試函數服務的公共函數、初始化函數、測試數據等,示例函數則是以Example開頭的說明被測試函數用法的函數。

大部分情況下,測試代碼是作為某個包的一部分,意味著它可以訪問包中不可導出的元素。但在有需要的時候(如避免循環依賴)也可以修改測試文件的包名,如package hello的測試文件,包名可以設為package hello_test。

功能測試函數

功能測試函數需要接收*testing.T類型的單一參數t,testing.T 類型用來管理測試狀態和支持格式化的測試日志。測試日志在測試執行過程中積累起來,完成后輸出到標準錯誤輸出。

下面是從Go標準庫摘抄的 testing.T類型的常用方法的用法:

測試函數中的某條測試用例執行結果與預期不符時,調用t.Error()t.Errorf()方法記錄日志并標記測試失敗

# /usr/local/go/src/bytes/compare_test.gofunc TestCompareIdenticalSlice(t *testing.T) { var b = []byte("Hello Gophers!") if Compare(b, b) != 0 {  t.Error("b != b") } if Compare(b, b[:1]) != 1 {  t.Error("b > b[:1] failed") }}

使用t.Fatal()t.Fatalf()方法,在某條測試用例失敗后就跳出該測試函數

# /usr/local/go/src/bytes/reader_test.gofunc TestReadAfterBigSeek(t *testing.T) { r := NewReader([]byte("0123456789")) if _, err := r.Seek(1<<31+5, os.SEEK_SET); err != nil {  t.Fatal(err) } if n, err := r.Read(make([]byte, 10)); n != 0 || err != io.EOF {  t.Errorf("Read = %d, %v; want 0, EOF", n, err) }}

使用t.Skip()t.Skipf()方法,跳過某條測試用例的執行

# /usr/local/go/src/archive/zip/zip_test.gofunc TestZip64(t *testing.T) { if testing.Short() {  t.Skip("slow test; skipping") } const size = 1 << 32 // before the "END/n" part buf := testZip64(t, size) testZip64DirectoryRecordLength(buf, t)}

執行測試用例的過程中通過t.Log()t.Logf()記錄日志

# /usr/local/go/src/regexp/exec_test.gofunc TestFowler(t *testing.T) { files, err := filepath.Glob("testdata/*.dat") if err != nil {  t.Fatal(err) } for _, file := range files {  t.Log(file)  testFowler(t, file) }}

使用t.Parallel()標記需要并發執行的測試函數

# /usr/local/go/src/runtime/stack_test.gofunc TestStackGrowth(t *testing.T) { t.Parallel() var wg sync.WaitGroup // in a normal goroutine wg.Add(1) go func() {  defer wg.Done()  growStack() }() wg.Wait() // ...}

性能測試函數

性能測試函數需要接收*testing.B類型的單一參數b,性能測試函數中需要循環b.N次調用被測函數。testing.B 類型用來管理測試時間和迭代運行次數,也支持和testing.T相同的方式管理測試狀態和格式化的測試日志,不一樣的是testing.B的日志總是會輸出。

下面是從Go標準庫摘抄的 testing.B類型的常用方法的用法:

在函數中調用t.ReportAllocs() ,啟用內存使用分析

# /usr/local/go/src/bufio/bufio_test.gofunc BenchmarkWriterFlush(b *testing.B) { b.ReportAllocs() bw := NewWriter(ioutil.Discard) str := strings.Repeat("x", 50) for i := 0; i < b.N; i++ {  bw.WriteString(str)  bw.Flush() }}

通過 b.StopTimer() 、b.ResetTimer() b.StartTimer()來停止、重置、啟動 時間經過和內存分配計數

# /usr/local/go/src/fmt/scan_test.gofunc BenchmarkScanInts(b *testing.B) { b.ResetTimer() ints := makeInts(intCount) var r RecursiveInt for i := b.N - 1; i >= 0; i-- {  buf := bytes.NewBuffer(ints)  b.StartTimer()  scanInts(&r, buf)  b.StopTimer() }}

調用b.SetBytes()記錄在一個操作中處理的字節數

# /usr/local/go/src/testing/benchmark.gofunc BenchmarkFields(b *testing.B) { b.SetBytes(int64(len(fieldsInput))) for i := 0; i < b.N; i++ {  Fields(fieldsInput) }}

通過b.RunParallel()方法和 *testing.PB類型的Next()方法來并發執行被測對象

# /usr/local/go/src/sync/atomic/value_test.gofunc BenchmarkValueRead(b *testing.B) { var v Value v.Store(new(int)) b.RunParallel(func(pb *testing.PB) {  for pb.Next() {   x := v.Load().(*int)   if *x != 0 {    b.Fatalf("wrong value: got %v, want 0", *x)   }  } })}

測試輔助代碼

測試輔助代碼是編寫測試代碼過程中因代碼重用和代碼質量考慮而產生的。主要包括如下方面:

引入依賴的外部包,如每個測試文件都需要的 testing 包等:

# /usr/local/go/src/log/log_test.go:import ( "bytes" "fmt" "os" "regexp" "strings" "testing" "time")

定義多次用到的常量和變量,測試用例數據等:

# /usr/local/go/src/log/log_test.go:const ( Rdate   = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]` Rtime   = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]` Rmicroseconds = `/.[0-9][0-9][0-9][0-9][0-9][0-9]` Rline   = `(57|59):` // must update if the calls to l.Printf / l.Print below move Rlongfile  = `.*/[A-Za-z0-9_/-]+/.go:` + Rline Rshortfile = `[A-Za-z0-9_/-]+/.go:` + Rline)// ...var tests = []tester{ // individual pieces: {0, "", ""}, {0, "XXX", "XXX"}, {Ldate, "", Rdate + " "}, {Ltime, "", Rtime + " "}, {Ltime | Lmicroseconds, "", Rtime + Rmicroseconds + " "}, {Lmicroseconds, "", Rtime + Rmicroseconds + " "}, // microsec implies time {Llongfile, "", Rlongfile + " "}, {Lshortfile, "", Rshortfile + " "}, {Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile // everything at once: {Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "}, {Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "},}

和普通的Golang源代碼一樣,測試代碼中也能定義init函數,init函數會在引入外部包、定義常量、聲明變量之后被自動調用,可以在init函數里編寫測試相關的初始化代碼。

# /usr/local/go/src/bytes/buffer_test.gofunc init() { testBytes = make([]byte, N) for i := 0; i < N; i++ {  testBytes[i] = 'a' + byte(i%26) } data = string(testBytes)}

封裝測試專用的公共函數,抽象測試專用的結構體等:

# /usr/local/go/src/log/log_test.go:type tester struct { flag int prefix string pattern string // regexp that log output must match; we add ^ and expected_text$ always}// ...func testPrint(t *testing.T, flag int, prefix string, pattern string, useFormat bool) { // ...}

示例函數

示例函數無需接收參數,但需要使用注釋的 Output: 標記說明示例函數的輸出值,未指定Output:標記或輸出值為空的示例函數不會被執行。

示例函數需要歸屬于某個 包/函數/類型/類型 的方法,具體命名規則如下:

func Example() { ... }  # 包的示例函數func ExampleF() { ... }  # 函數F的示例函數func ExampleT() { ... }  # 類型T的示例函數func ExampleT_M() { ... } # 類型T的M方法的示例函數# 多示例函數 需要跟下劃線加小寫字母開頭的后綴func Example_suffix() { ... }func ExampleF_suffix() { ... }func ExampleT_suffix() { ... }func ExampleT_M_suffix() { ... }

go doc 工具會解析示例函數的函數體作為對應 包/函數/類型/類型的方法 的用法。

測試函數的相關說明,可以通過go help testfunc來查看幫助文檔。

使用 go test 工具

Golang中通過命令行工具go test來執行測試代碼,打開shell終端,進入需要測試的包所在的目錄執行 go test,或者直接執行go test $pkg_name_in_gopath即可對指定的包執行測試。

通過形如go test github.com/tabalt/...的命令可以執行$GOPATH/github.com/tabalt/目錄下所有的項目的測試。go test std命令則可以執行Golang標準庫的所有測試。

如果想查看執行了哪些測試函數及函數的執行結果,可以使用-v參數:

[tabalt@localhost hello] go test -v=== RUN TestHello--- PASS: TestHello (0.00s)=== RUN ExampleHello--- PASS: ExampleHello (0.00s)PASSok  hello 0.006s

假設我們有很多功能測試函數,但某次測試只想執行其中的某一些,可以通過-run參數,使用正則表達式來匹配要執行的功能測試函數名。如下面指定參數后,功能測試函數TestHello不會執行到。

[tabalt@localhost hello] go test -v -run=xxxPASSok  hello 0.006s

性能測試函數默認并不會執行,需要添加-bench參數,并指定匹配性能測試函數名的正則表達式;例如,想要執行某個包中所有的性能測試函數可以添加參數-bench . 或 -bench=.。

[tabalt@localhost hello] go test -bench=.PASSBenchmarkHello-8  2000000   657 ns/opok  hello 1.993s

想要查看性能測試時的內存情況,可以再添加參數-benchmem:

[tabalt@localhost hello] go test -bench=. -benchmemPASSBenchmarkHello-8  2000000   666 ns/op   208 B/op   9 allocs/opok  hello 2.014s

參數-cover可以用來查看我們編寫的測試對代碼的覆蓋率:

詳細的覆蓋率信息,可以通過-coverprofile輸出到文件,并使用go tool cover來查看,用法請參考go tool cover -help 。

更多go test命令的參數及用法,可以通過go help testflag來查看幫助文檔。

高級測試技術

IO相關測試

testing/iotest包中實現了常用的出錯的Reader和Writer,可供我們在io相關的測試中使用。主要有:

觸發數據錯誤dataErrReader,通過DataErrReader()函數創建

讀取一半內容的halfReader,通過HalfReader()函數創建

讀取一個byte的oneByteReader,通過OneByteReader()函數創建

觸發超時錯誤的timeoutReader,通過TimeoutReader()函數創建

寫入指定位數內容后停止的truncateWriter,通過TruncateWriter()函數創建

讀取時記錄日志的readLogger,通過NewReadLogger()函數創建

寫入時記錄日志的writeLogger,通過NewWriteLogger()函數創建

黑盒測試

testing/quick包實現了幫助黑盒測試的實用函數 Check和CheckEqual。

Check函數的第1個參數是要測試的只返回bool值的黑盒函數f,Check會為f的每個參數設置任意值并多次調用,如果f返回false,Check函數會返回錯誤值 *CheckError。Check函數的第2個參數 可以指定一個quick.Config類型的config,傳nil則會默認使用quick.defaultConfig。quick.Config結構體包含了測試運行的選項。

# /usr/local/go/src/math/big/int_test.gofunc checkMul(a, b []byte) bool { var x, y, z1 Int x.SetBytes(a) y.SetBytes(b) z1.Mul(&x, &y) var z2 Int z2.SetBytes(mulBytes(a, b)) return z1.Cmp(&z2) == 0}func TestMul(t *testing.T) { if err := quick.Check(checkMul, nil); err != nil {  t.Error(err) }}

CheckEqual函數是比較給定的兩個黑盒函數是否相等,函數原型如下:

func CheckEqual(f, g interface{}, config *Config) (err error)

HTTP測試

net/http/httptest包提供了HTTP相關代碼的工具,我們的測試代碼中可以創建一個臨時的httptest.Server來測試發送HTTP請求的代碼:

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, client")}))defer ts.Close()res, err := http.Get(ts.URL)if err != nil { log.Fatal(err)}greeting, err := ioutil.ReadAll(res.Body)res.Body.Close()if err != nil { log.Fatal(err)}fmt.Printf("%s", greeting)

還可以創建一個應答的記錄器httptest.ResponseRecorder來檢測應答的內容:

handler := func(w http.ResponseWriter, r *http.Request) { http.Error(w, "something failed", http.StatusInternalServerError)}req, err := http.NewRequest("GET", "http://example.com/foo", nil)if err != nil { log.Fatal(err)}w := httptest.NewRecorder()handler(w, req)fmt.Printf("%d - %s", w.Code, w.Body.String())

測試進程操作行為

當我們被測函數有操作進程的行為,可以將被測程序作為一個子進程執行測試。下面是一個例子:

//被測試的進程退出函數func Crasher() { fmt.Println("Going down in flames!") os.Exit(1)}//測試進程退出函數的測試函數func TestCrasher(t *testing.T) { if os.Getenv("BE_CRASHER") == "1" {  Crasher()  return } cmd := exec.Command(os.Args[0], "-test.run=TestCrasher") cmd.Env = append(os.Environ(), "BE_CRASHER=1") err := cmd.Run() if e, ok := err.(*exec.ExitError); ok && !e.Success() {  return } t.Fatalf("process ran with err %v, want exit status 1", err)}

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家學習或者使用Go語言能有所幫助,如果有疑問大家可以留言交流。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲免费高清视频| 欧美日韩国产限制| 久久亚洲精品国产亚洲老地址| 国产精品劲爆视频| 亚洲欧美国产制服动漫| 亚洲精品国产免费| 精品亚洲一区二区三区在线观看| 国产精品毛片a∨一区二区三区|国| 久久精品在线视频| 91久久精品国产91久久| 中文字幕亚洲天堂| 在线亚洲午夜片av大片| 日韩免费av片在线观看| 欧美一区二区三区……| 欧美激情亚洲一区| 久久久亚洲国产| 国产成人精品久久| 欧美黑人一区二区三区| 亚洲综合大片69999| 精品久久久国产精品999| 国产精品久久77777| 亚洲精品免费在线视频| 一区二区三区四区视频| 2019中文字幕免费视频| 亚洲人成在线电影| 另类少妇人与禽zozz0性伦| 91久久国产精品91久久性色| 欧美性猛交xxxx免费看| 性欧美暴力猛交69hd| 国产精品偷伦免费视频观看的| 97超碰蝌蚪网人人做人人爽| 亚洲专区国产精品| 97涩涩爰在线观看亚洲| 国产精品久久久久久久一区探花| 一本色道久久88综合亚洲精品ⅰ| 日韩av快播网址| 免费不卡欧美自拍视频| 日本免费在线精品| 色综合久久88| 亚洲精品影视在线观看| 亚洲中国色老太| 超碰精品一区二区三区乱码| 国产精品一二三视频| 日韩亚洲欧美成人| 久久综合国产精品台湾中文娱乐网| 538国产精品一区二区在线| 国产一区欧美二区三区| 伊人久久大香线蕉av一区二区| 国产精品久久久久久久久免费看| 国产精品一区二区三区毛片淫片| 97香蕉超级碰碰久久免费的优势| 亚洲国模精品私拍| 亚洲福利在线播放| 久久精品国产96久久久香蕉| 国产亚洲精品久久久久久| 亚洲一区二区三区在线免费观看| 久久精彩免费视频| 亚洲欧美日韩天堂一区二区| 日韩精品丝袜在线| 久久露脸国产精品| 高清欧美性猛交| 欧美电影在线观看完整版| 欧美大码xxxx| 国产精品日韩欧美大师| 日韩中文字幕视频| 91禁外国网站| 亚洲最大成人免费视频| 日韩精品视频在线播放| 亚洲欧美国产日韩中文字幕| 最近2019中文字幕大全第二页| 俺去亚洲欧洲欧美日韩| 国产精品成av人在线视午夜片| 成人免费网站在线看| 日韩免费av片在线观看| 色噜噜国产精品视频一区二区| 狠狠色香婷婷久久亚洲精品| 久久精品国产视频| 久久香蕉国产线看观看网| 成人性生交xxxxx网站| 成人性生交大片免费观看嘿嘿视频| 伊人av综合网| 亚洲精品久久7777777| xxxxx成人.com| 成人免费视频在线观看超级碰| 热久久视久久精品18亚洲精品| 亚洲欧洲一区二区三区久久| 在线观看视频99| 亚洲色图18p| 97在线视频免费观看| 亚洲欧洲成视频免费观看| 成人久久精品视频| 黑人巨大精品欧美一区二区一视频| 中文字幕日韩av综合精品| 久久久久久久久中文字幕| 91免费看片在线| 伊人久久精品视频| 欧美与黑人午夜性猛交久久久| 精品一区二区三区四区在线| 亚洲精品网址在线观看| 国产精品女人网站| 亚洲成色999久久网站| 精品亚洲精品福利线在观看| 亚洲免费电影在线观看| 欧美视频中文在线看| 欧美一级bbbbb性bbbb喷潮片| 欧美国产亚洲视频| 亚洲人成在线一二| 欧美性生活大片免费观看网址| 亚洲男人天堂2019| 欧美人在线视频| 欧美午夜视频一区二区| 国产精品亚洲аv天堂网| 2019中文字幕在线| 欧美性猛交xxxx乱大交蜜桃| 欧美午夜www高清视频| 中文字幕不卡av| 2019中文字幕在线| 欧美日韩精品二区| 日韩成人在线视频网站| 操日韩av在线电影| 欧美与黑人午夜性猛交久久久| 91嫩草在线视频| 亚洲精品久久久久| 久久久精品久久久| 在线看日韩av| 青青草精品毛片| 国产精品一区久久| 国产日韩在线一区| 国产成人精品国内自产拍免费看| 九九视频直播综合网| 国产偷国产偷亚洲清高网站| 亚洲综合色av| 亚洲欧美国产一区二区三区| 性色av一区二区三区在线观看| 性色av一区二区三区免费| 久久亚洲国产精品| 亚洲日韩第一页| 日韩av中文在线| 亚洲视频国产视频| 色综合伊人色综合网站| 91av成人在线| 中文欧美日本在线资源| 日韩中文字在线| 在线成人激情视频| 91影院在线免费观看视频| 亚洲一区久久久| 国产一区av在线| 欧美日韩国产精品一区二区不卡中文| 国产精品久久久久影院日本| 国产噜噜噜噜久久久久久久久| 久久伊人91精品综合网站| 久久夜色精品国产亚洲aⅴ| 国产美女久久久| 欧美成aaa人片在线观看蜜臀| 欧美日韩亚洲一区二区| 欧美性一区二区三区| 综合激情国产一区| 精品久久中文字幕| 国内精品久久久久影院 日本资源| 亚洲国产美女精品久久久久∴| 久久视频这里只有精品| 亚洲美女av电影| 日韩高清av一区二区三区| 一本一道久久a久久精品逆3p|