相信咱們作程序員的,對單元測試都不陌生。單元測試通常是用來測試咱們的代碼邏輯有沒有問題,有沒有按照咱們指望的運行,以保證代碼質量。html
大多數的單元測試,都是對某一個函數方法進行測試,以儘量的保證沒有問題或者問題可被咱們預知。爲了達到這個目的,咱們可使用各類手段、邏輯,模擬不一樣的場景進行測試。程序員
這裏咱們在package main裏定義一個函數Add,求兩個數之和的函數,而後咱們使用單元測試進行求和邏輯測試。json
main.go服務器
func Add(a,b int) int{ return a+b
}
main_test.go網絡
func TestAdd(t *testing.T) { sum := Add(1,2) if sum == 3 { t.Log("the result is ok") } else { t.Fatal("the result is wrong") }
}
而後咱們在終端的項目目錄下運行go test -v就能夠看到測試結果了。app
➜ hello go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s) main_test.go:26: the result is ok PASS ok flysnow.org/hello 0.007s
有測試成功PASS標記,而且打印出咱們想要的結果。框架
Go語言爲咱們提供了測試框架,以便幫助咱們更容易的進行單元測試,可是要使用這個框架,須要遵循以下幾點規則:ide
含有單元測試代碼的go文件必須以_test.go結尾,Go語言測試工具只認符合這個規則的文件。函數
單元測試文件名_test.go前面的部分最好是被測試的方法所在go文件的文件名,好比例子中是main_test.go,由於測試的Add函數,在main.go文件裏。工具
單元測試的函數名必須以Test開頭,是可導出公開的函數。
測試函數的簽名必須接收一個指向testing.T類型的指針,而且不能返回任何值。
函數名最好是Test+要測試的方法函數名,好比例子中是TestAdd,表示測試的是Add這個這個函數。
遵循以上規則,咱們就能夠很容易的編寫單元測試了,單元測試的重點在於測試代碼的邏輯,場景等,以便儘量的測試全面,保障代碼質量邏輯。
還有一種單元測試方法叫表組測試,這個和基本的單元測試很是類似,只不過它是有好幾個不一樣的輸入以及輸出組成的一組單元測試。
好比上個例子中,咱們測試了1+2,若是咱們再加上3+4,9+2等,這就有了好幾個輸入,同時對應的也有好幾個輸出,這種一次性測試不少個輸入輸出場景的測試,就是表組測試。
func TestAdd(t *testing.T) { sum := Add(1,2) if sum == 3 { t.Log("the result is ok") } else { t.Fatal("the result is wrong") } sum=Add(3,4) if sum == 7 { t.Log("the result is ok") } else { t.Fatal("the result is wrong") }
}
單元測試的原則,就是你所測試的函數方法,不要受到所依賴環境的影響,好比網絡訪問等,由於有時候咱們運行單元測試的時候,並無聯網,那麼總不能讓單元測試由於這個失敗吧?因此這時候模擬網絡訪問就有必要了。
針對模擬網絡訪問,標準庫了提供了一個httptest包,可讓咱們模擬http的網絡調用,下面舉個例子瞭解使用。
首先咱們建立一個處理HTTP請求的函數,並註冊路由。
package commonimport ( "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)
}
很是簡單,這裏是一個/sendjsonAPI,當咱們訪問這個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())
}
運行這個單元測試,就能夠看到咱們訪問/sendjsonAPI的結果裏,而且咱們沒有啓動任何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同樣了,打印出結果便可。
咱們儘量的模擬更多的場景來測試咱們代碼的不一樣狀況,可是有時候的確也有忘記測試的代碼,這時候咱們就須要測試覆蓋率做爲參考了。
由單元測試的代碼,觸發運行到的被測試代碼的代碼行數佔全部代碼行數的比例,被稱爲測試覆蓋率,代碼覆蓋率不必定徹底精準,可是能夠做爲參考,能夠幫咱們測量和咱們預計的覆蓋率之間的差距,go test工具,就爲咱們提供了這麼一個度量測試覆蓋率的能力。
main.go
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
func TestTag(t *testing.T) { Tag(1) Tag(2)
}
如今咱們使用go test工具運行單元測試,和前幾回不同的是,咱們要顯示測試覆蓋率,因此要多加一個參數-coverprofile,因此完整的命令爲:go test -v -coverprofile=c.out,-coverprofile是指定生成的覆蓋率文件,例子中是c.out,這個文件一會咱們會用到。如今咱們看終端輸出,已經有了一個覆蓋率。
=== RUN TestTag
Android
Go
--- PASS: TestTag (0.00s)
PASS coverage: 60.0% of statements ok flysnow.org/hello 0.005s
coverage: 60.0% of statements,60% 的測試覆蓋率,尚未到 100% ,那麼咱們看看還有那些代碼沒有被測試到。這就須要咱們剛剛生成的測試覆蓋率文件c.out生成測試覆蓋率報告了。生成報告有go爲咱們提供的工具,使用go tool cover -html=c.out -o=tag.html,便可生成一個名字爲tag.html的HTML格式的測試覆蓋率報告,這裏有詳細的信息告訴咱們哪一行代碼測試到了,哪一行代碼沒有測試到。
從上圖中能夠看到,標記爲綠色的代碼行已經被測試了;標記爲紅色的尚未測試到,有兩行的,如今咱們根據沒有測試到的代碼邏輯,完善個人單元測試代碼便可。
func TestTag(t *testing.T) { Tag(1) Tag(2) Tag(3) Tag(6)
}
單元測試完善爲如上代碼,再運行單元測試,就能夠看到測試覆蓋率已是 100% 了,大功告成。