這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在用Gin架構編寫了一個web server之後,我們如果需要測試handlers介面函數的話,主要可以採用兩種方式來進行。
第一種是部署web server,然後通過瀏覽器或其他http請求類比工具來手動類比真實的http請求,發送http請求之後,解析返回的響應,查看響應是否符合預期;這種做法比較麻煩,而且測試結果不太可靠。
第二種是使用httptest結合testing來實現針對handlers介面函數的單元測試。
下面以對四個介面做相應的單元測試為例,分享基於Gin的單元測試的一些方法:
介面名稱 |
請求地址 |
請求類型 |
響應資料類型 |
響應資料 |
OnGetStringRequest |
/getString |
get |
string |
success |
OnPracticeRequest |
/practice |
get |
html頁面 |
practice.html |
OnLoginRequestForForm |
/loginForm |
post |
json |
|
OnLoginRequestForJson |
/loginJson |
post |
json |
|
一、樣本介面代碼:
OnGetStringRequest:
// OnGetStringRequest 返回success字串的介面func OnGetStringRequest(c *gin.Context) { c.String(http.StatusOK, "success")}
OnPracticeRequest:
// OnPracticeRequest 返回practice.html頁面的介面func OnPracticeRequest(c *gin.Context) { c.HTML(http.StatusOK,"practice.html",gin.H{})}
OnLoginRequestForForm:
// OnLoginRequestForForm 以表單形式傳遞參數的登入介面func OnLoginRequestForForm(c *gin.Context) { req := &User{} if err := c.ShouldBindWith(req, binding.Form); err != nil { log.Printf("err:%v",err) c.JSON(http.StatusOK, gin.H{ "errno":"1", "errmsg":"參數不匹配", "data":"", }) return } c.JSON(http.StatusOK, gin.H{ "errno":"0", "errmsg":"", "data":req, })}
OnLoginRequestForJson:
// OnLoginRequestForJson 以Json形式傳遞參數的登入介面func OnLoginRequestForJson(c *gin.Context) { req := &User{} if err := c.ShouldBindWith(req, binding.JSON); err != nil { log.Printf("err:%v",err) c.JSON(http.StatusOK, gin.H{ "errno":"1", "errmsg":"參數不匹配", "data":"", }) return } c.JSON(http.StatusOK, gin.H{ "errno":"0", "errmsg":"", "data":req, })}
二、調用的一些結構體和工具函數:
User結構體代碼:
//承接前端傳過來的json資料或form表單資料type User struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` Age int `form:"age" json:"age" binding:"required"`}
LoginResponse結構體代碼:
// LoginResponse 登入介面的響應參數type LoginResponse struct { Errno string `json:"errno"` Errmsg string `json:"errmsg"` Data User `json:"data"`}
調用的工具函數:
// ParseToStr 將map中的索引值對輸出成querystring形式func ParseToStr(mp map[string]string) string { values := "" for key, val := range mp { values += "&" + key + "=" + val } temp := values[1:] values = "?" + temp return values}
三、單元測試編寫步驟:
1. 初始化路由
func init(){
// 初始化路由
router = gin.Default()
router.GET("/getString", OnGetStringRequest)
router.POST("/loginForm", OnLoginRequestForForm)
router.POST("/loginJson", OnLoginRequestForJson)
router.LoadHTMLGlob("E:/mygo/resources/pages/*") //定義模板檔案路徑
router.GET("/practice", OnPracticeRequest)
}
當介面涉及到對資料庫的相關操作時,可以將資料庫服務以中介軟體的形式加到gin的Context中如所示:
2. 封裝構造http請求的函數(以便測試函數直接調用發起不同種類的http請求)
2.1構造get請求
// Get 根據特定請求uri,發起get請求返迴響應func Get(uri string, router *gin.Engine) []byte { // 構造get請求 req := httptest.NewRequest("GET", uri, nil) // 初始化響應 w := httptest.NewRecorder() // 調用相應的handler介面 router.ServeHTTP(w, req) // 提取響應 result := w.Result() defer result.Body.Close() // 讀取響應body body,_ := ioutil.ReadAll(result.Body) return body}
2.2構造post請求,以表單形式傳遞參數
// PostForm 根據特定請求uri和參數param,以表單形式傳遞參數,發起post請求返迴響應func PostForm(uri string, param map[string]string, router *gin.Engine) []byte { // 構造post請求,表單資料以querystring的形式加在uri之後 req := httptest.NewRequest("POST", uri+ParseToStr(param), nil) // 初始化響應 w := httptest.NewRecorder() // 調用相應handler介面 router.ServeHTTP(w, req) // 提取響應 result := w.Result() defer result.Body.Close() // 讀取響應body body, _ := ioutil.ReadAll(result.Body) return body}
2.3構造post請求,以Json形式傳遞參數
// PostJson 根據特定請求uri和參數param,以Json形式傳遞參數,發起post請求返迴響應func PostJson(uri string, param map[string]interface{}, router *gin.Engine) []byte { // 將參數轉化為json位元流 jsonByte,_ := json.Marshal(param) // 構造post請求,json資料以請求body的形式傳遞 req := httptest.NewRequest("POST", uri, bytes.NewReader(jsonByte)) // 初始化響應 w := httptest.NewRecorder() // 調用相應的handler介面 router.ServeHTTP(w, req) // 提取響應 result := w.Result() defer result.Body.Close() // 讀取響應body body,_ := ioutil.ReadAll(result.Body) return body}
3. 編寫測試函數
3.1針對OnGetStringRequest介面的測試函數
// TestOnGetStringRequest 測試以Get方式擷取一段字串的介面func TestOnGetStringRequest(t *testing.T) { // 初始化請求地址 uri := "/getString" // 發起Get請求 body := Get(uri, router) fmt.Printf("response:%v\n", string(body)) // 判斷響應是否與預期一致 if string(body) != "success" { t.Errorf("響應字串不符,body:%v\n",string(body)) }}
3.2針對OnPracticeRequest介面的測試函數
// TestOnPracticeRequest 測試以Get方式擷取practice.html頁面的介面func TestOnPracticeRequest(t *testing.T) { // 初始化請求地址 uri := "/practice" // 發起Get請求 body := Get(uri, router) fmt.Printf("response:%v\n", string(body)) // 判斷響應是否與預期一致 html,_ := ioutil.ReadFile("E:/mygo/resources/pages/practice.html") htmlStr := string(html) if htmlStr != string(body) { t.Errorf("響應資料不符,body:%v\n",string(body)) }}
3.3針對OnLoginRequestForForm介面的測試函數
// TestOnLoginRequestForForm 測試以表單形式傳遞參數的登入介面func TestOnLoginRequestForForm(t *testing.T) { // 初始化請求地址和請求參數 uri := "/loginForm" param := make(map[string]string) param["username"] = "valiben" param["password"] = "123" param["age"] = "1" // 發起post請求,以表單形式傳遞參數 body := PostForm(uri, param, router) fmt.Printf("response:%v\n", string(body)) // 解析響應,判斷響應是否與預期一致 response := &LoginResponse{} if err := json.Unmarshal(body, response); err != nil { t.Errorf("解析響應出錯,err:%v\n",err) } if response.Data.Username != "valiben" { t.Errorf("響應資料不符,username:%v\n",response.Data.Username) }}
3.4針對OnLoginRequestForJson介面的測試函數
// TestOnLoginRequestForJson 測試以Json形式傳遞參數的登入介面func TestOnLoginRequestForJson(t *testing.T) { // 初始化請求地址和請求參數 uri := "/loginJson" param := make(map[string]interface{}) param["username"] = "valiben" param["password"] = "123" param["age"] = 1 // 發起post請求,以Json形式傳遞參數 body := PostJson(uri, param, router) fmt.Printf("response:%v\n", string(body)) // 解析響應,判斷響應是否與預期一致 response := &LoginResponse{} if err := json.Unmarshal(body, response); err != nil { t.Errorf("解析響應出錯,err:%v\n",err) } if response.Data.Username != "valiben" { t.Errorf("響應資料不符,username:%v\n",response.Data.Username) }}
4. 運行單元測試,查看測試結果
執行 go test ./ 運行測試代碼,測試結果如下
四、總結
基於Gin的單元測試的主要步驟就是先要初始化路由,設定handler介面函數攔截的http請求地址(不需要設定監聽連接埠號碼);然後通過"net/http/httptest"包的NewRequest(method, target string, body io.Reader)方法構造request,第一個參數是請求類型“POST”“GET”之類,第二個參數是請求的URI地址(form表單的參數可以通過querystring的形式附在URI地址後面進行傳遞),第三個參數是請求的請求body內容(json資料等其他類型的資料可以加在這裡進行傳遞);接著通過NewRecorder()函數構造響應,調用func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request)方法來調用請求處理的介面handlers,返回的響應將寫入前面構造的響應中,通過解析響應,查看其中資料即可完成對介面的測試