測試的目的是確認目標代碼在給定的場景下,有沒有按照指望工做 。一個場景是正向路經測試,就是在正常執行的狀況下,保證代碼不產生錯誤的測試。
另一些單元測試可能會測試負向路徑的場景,保證代碼不只會產生錯誤,並且是預期的錯誤。總之,無論如何調用或者執行代碼,所寫的代碼行爲都是可預期的 html
go語言爲咱們提供了測試框架testing和自帶go test命令來實現單元測試和性能測試。json
go語言自帶的testing 框架使用:windows
func TestAdd(t *testing.T)()
好比咱們有一個函數Sum(a,b int) int 須要測試,bash
main.go服務器
package main func Sum(a,b int )int{ return a+b }
main_test.go網絡
package main import ( "testing" ) func TestSum(t *testing.T){ sum := Sum(1, 2) if sum==3{ t.Logf("成功,結果爲%v",sum) }else{ t.Fatalf("有錯誤,結果爲%v",sum) } }
運行測試命令;併發
//命令行執行 go test 命令 PASS ok _/D_/gopath/src/a_tour_of_go/testing 1.301s //執行go test -v 命令 === RUN TestSum --- PASS: TestSum (0.00s) main_test.go:10: 成功,結果爲3 PASS ok _/D_/gopath/src/a_tour_of_go/testing 1.302s //執行 go test -v main_test.go main.go 結果同上,由於咱們就寫了一個測試文件。 //執行 go test -v -test.run TestSum 結果同上,由於只有一個函數
其中,*testing.T 有一些方法方便使用app
type T func (c *T) Error(args ...interface{}) func (c *T) Errorf(format string, args ...interface{}) func (c *T) Fail() func (c *T) FailNow() func (c *T) Failed() bool func (c *T) Fatal(args ...interface{}) func (c *T) Fatalf(format string, args ...interface{}) func (c *T) Helper() func (c *T) Log(args ...interface{}) func (c *T) Logf(format string, args ...interface{}) func (c *T) Name() string func (t *T) Parallel() func (t *T) Run(name string, f func(t *T)) bool func (c *T) Skip(args ...interface{}) func (c *T) SkipNow() func (c *T) Skipf(format string, args ...interface{}) func (c *T) Skipped() bool
好比咱們經常使用,Fatalf() 出現錯誤是調用會打印,而且結束函數, Logf() 打印自定義信息。框架
所謂的表組測試,基本上和單元測試同樣,只不過它有好幾個不一樣輸入以及輸出組成的一組單元測試。函數
簡單修改:無非就是多測幾回不一樣的值唄
package main import ( "testing" ) func TestSum(t *testing.T){ sum := Sum(1, 2) if sum==3{ t.Logf("成功,結果爲%v",sum) }else{ t.Fatalf("有錯誤,結果爲%v",sum) } sum1 := Sum(4, 4) if sum1==8{ t.Logf("成功,結果爲%v",sum1) }else{ t.Fatalf("有錯誤,結果爲%v",sum1) } sum2 := Sum(5, 5) if sum2==10{ t.Logf("成功,結果爲%v",sum2) }else{ t.Fatalf("有錯誤,結果爲%v",sum2) } } //結果 === RUN TestSum --- PASS: TestSum (0.00s) main_test.go:10: 成功,結果爲3 main_test.go:16: 成功,結果爲8 main_test.go:22: 成功,結果爲10 PASS ok a_tour_of_go/testing 1.253s
當咱們測試須要網絡訪問時,咱們並無聯網,又不能時時開啓服務器,因此這時候模擬網絡訪問就有必要了。
針對模擬網絡訪問,標準庫了提供了一個httptest
包,可讓咱們模擬http的網絡調用。
首先咱們建立一個處理HTTP請求的函數,並註冊路由
package common import ( "net/http" "encoding/json" ) func Routes(){ http.HandleFunc("/sendjson",SendJSON) } func SendJSON(rw http.ResponseWriter,r *http.Request){ u := struct { Name string }{ Name:"張三", } rw.Header().Set("Content-Type","application/json") rw.WriteHeader(http.StatusOK) json.NewEncoder(rw).Encode(u) }
很是簡單,這裏是一個/sendjson
API,當咱們訪問這個API時,會返回一個JSON字符串。如今咱們對這個API服務進行測試,可是咱們又不能時時刻刻都啓動着服務,因此這裏就用到了外部終端對API的網絡訪問請求。
func init() { common.Routes() } func TestSendJSON(t *testing.T){ req,err:=http.NewRequest(http.MethodGet,"/sendjson",nil) if err!=nil { t.Fatal("建立Request失敗") } rw:=httptest.NewRecorder() http.DefaultServeMux.ServeHTTP(rw,req) log.Println("code:",rw.Code) log.Println("body:",rw.Body.String()) }
運行這個單元測試,就能夠看到咱們訪問/sendjson
API的結果裏,而且咱們沒有啓動任何HTTP服務就達到了目的。這個主要利用httptest.NewRecorder()
建立一個http.ResponseWriter
,模擬了真實服務端的響應,這種響應時經過調用http.DefaultServeMux.ServeHTTP
方法觸發的。
還有一個模擬調用的方式,是真的在測試機上模擬一個服務器,而後進行調用測試。
func mockServer() *httptest.Server { //API調用處理函數 sendJson := func(rw http.ResponseWriter, r *http.Request) { u := struct { Name string }{ Name: "張三", } rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusOK) json.NewEncoder(rw).Encode(u) } //適配器轉換 return httptest.NewServer(http.HandlerFunc(sendJson)) } func TestSendJSON(t *testing.T) { //建立一個模擬的服務器 server := mockServer() defer server.Close() //Get請求發往模擬服務器的地址 resq, err := http.Get(server.URL) if err != nil { t.Fatal("建立Get失敗") } defer resq.Body.Close() log.Println("code:", resq.StatusCode) json, err := ioutil.ReadAll(resq.Body) if err != nil { log.Fatal(err) } log.Printf("body:%s\n", json) }
模擬服務器的建立使用的是httptest.NewServer
函數,它接收一個http.Handler
處理API請求的接口。 代碼示例中使用了Hander的適配器模式,http.HandlerFunc
是一個函數類型,實現了http.Handler
接口,這裏是強制類型轉換,不是函數的調用。
這個建立的模擬服務器,監聽的是本機IP127.0.0.1
,端口是隨機的。接着咱們發送Get請求的時候,再也不發往/sendjson
,而是模擬服務器的地址server.URL
,剩下的就和訪問正常的URL同樣了,打印出結果便可。
就其性質而言,測試不多是完整的 。再多測試也不能說明程序沒有bug,測試能夠加強咱們的信心,讓咱們的程序在一個放心的環境中正常運行。
由單元測試的代碼,觸發運行到的被測試代碼的代碼行數佔全部代碼行數的比例,被稱爲測試覆蓋率,代碼覆蓋率不必定徹底精準,可是能夠做爲參考,能夠幫咱們測量和咱們預計的覆蓋率之間的差距,go test
工具,就爲咱們提供了這麼一個度量測試覆蓋率的能力。
簡單來講就是一個參數 - coverprofile
好比:
main.go
package main import "fmt" func Tag(tag int){ switch tag { case 1: fmt.Println("Android") case 2: fmt.Println("Go") case 3: fmt.Println("Java") default: fmt.Println("C") } }
main_test.go
package main import ( "testing" ) func TestTag(t *testing.T) { Tag(1) Tag(2) }
執行命令: go test -v -coverprofile=c.out
輸出結果:
=== RUN TestTag
Android
Go
--- PASS: TestTag (0.00s)
PASS
coverage: 60.0% of statements
ok a_tour_of_go/testing 1.309s
獲得測試覆蓋率爲60% , 咱們以前的c.out 是生成的測試報告,咱們能夠看到當前目錄下有一個c.out文件
咱們能夠生成 html 文件 go tool cover -html=c.out
會直接打開一個網頁,顯示咱們的代碼,經過顏色區分。 固然也能夠go tool cover -html=c.out -o=tag.html
在當前目錄生成一個名爲tag.html的文件,雙擊打開,同樣的。
標記爲綠色的代碼行已經被測試了;標記爲紅色的尚未測試到。咱們根據沒有測試到的代碼邏輯,完善個人單元測試代碼便可。
基準測試是一種測試代碼性能的方法。想要測試解決同一問題的不一樣方案的性能,以及查看哪一種解決方案的性能更好時,基準測試就會頗有用。
基準測試也能夠用來識別某段代碼的 CPU或者內存效率問題,而這段代碼的效率可能會嚴重影響整個應用程序的性能。許多開發人員會用基準測試來測試不一樣的併發模式,或者用基準測試來輔助配置工做池的數量,以保證能最大化系統的吞吐量。
main_test.go
package main import ( "fmt" "testing" ) func BenchmarkSprintf(b *testing.B){ num:=10 b.ResetTimer()//重置計時器 for i:=0;i<b.N;i++{ fmt.Sprintf("%d",num) } }
這是一個基準測試的例子,從中咱們能夠看出如下規則:
b.ResetTimer
是重置計時器,這樣能夠避免for循環以前的初始化代碼的干擾運行命令go test -v -run=none -bench=.
查看結果
goos: windows goarch: amd64 BenchmarkSprintf-8 10000000 134 ns/op PASS ok _/D_/gopath/src/a_tour_of_go/testing 2.797s
-bench
:是進行基準測試參數, =. 表示全部的函數,若是要特定函數只須要後面跟函數名,好比 -bench=BenchmarkSprintf
-run=none
:的做用是,運行一個none 不存在的單元測試,避免單元測試輸出干擾。由於運行基準測試的時候是默認運行咱們的單元測試的。咱們爲了查看方便,就運行一個不存在的單元測試過濾掉。
輸出結果表示:
看到函數後面的-8
了嗎?這個表示運行時對應的GOMAXPROCS的值。接着的10000000
表示運行for循環的次數,也就是調用被測試代碼的次數,最後的134 ns/op
表示每次須要話費134納秒。
以上是測試時間默認是1秒,也就是1秒的時間,調用1000萬次,每次調用花費134納秒。若是想讓測試運行的時間更長,能夠經過-benchtime
指定,好比3秒。
go test -bench=. -benchtime=3s -run=none
goos: windows goarch: amd64 BenchmarkSprintf-8 30000000 121 ns/op PASS ok _/D_/gopath/src/a_tour_of_go/testing 5.731s
上面那個基準測試的例子,實際上是一個int類型轉爲string類型的例子,標準庫裏還有幾種方法,咱們看下哪一種性能更加。
func BenchmarkSprintf(b *testing.B){ num:=10 b.ResetTimer() for i:=0;i<b.N;i++{ fmt.Sprintf("%d",num) } } func BenchmarkFormat(b *testing.B){ num:=int64(10) b.ResetTimer() for i:=0;i<b.N;i++{ strconv.FormatInt(num,10) } } func BenchmarkItoa(b *testing.B){ num:=10 b.ResetTimer() for i:=0;i<b.N;i++{ strconv.Itoa(num) } }
運行基準測試,看看結果
運行命令: go test -bench=. -run=none goos: windows goarch: amd64 BenchmarkSprintf-8 10000000 126 ns/op BenchmarkFormat-8 300000000 3.85 ns/op BenchmarkItoa-8 300000000 3.93 ns/op PASS ok _/D_/gopath/src/a_tour_of_go/testing 5.893s
從結果上看strconv.FormatInt
函數是最快的,其次是strconv.Itoa
,而後是fmt.Sprintf
最慢。第一個最慢,咱們能夠經過-benchmem
找到根本緣由。
運行命令: go test -bench=. -benchmem -run=none goos: windows goarch: amd64 BenchmarkSprintf-8 10000000 132 ns/op 16 B/op 2 allocs/op BenchmarkFormat-8 300000000 3.97 ns/op 0 B/op 0 allocs/op BenchmarkItoa-8 300000000 4.25 ns/op 0 B/op 0 allocs/op PASS ok _/D_/gopath/src/a_tour_of_go/testing 6.073s
-benchmem
能夠提供每次操做分配內存的次數,以及每次操做分配的字節數。結果顯示,效率高的兩個每次操做進行0次內存分配,每次分配操做0個字節,多是這個太簡單了,因此沒來得及分配。慢的那個每次操做進行2次內存分配,每次16個字節。 因此效率高低的緣由一目瞭然了。
在代碼開發中,對於咱們要求性能的地方,編寫基準測試很是重要,這有助於咱們開發出性能更好的代碼。不過性能、可用性、複用性等也要有一個相對的取捨,不能爲了追求性能而過分優化。