目前我们的后端服务提供大量的restful api接口,每次上线都需要测试那边回归一遍这些接口,造成人力的浪费。正好借着这次单元测试和持续集成,我们引入了httptest框架,结合gin来做接口单元测试。
httptest是golang官方提供的一个包,位于/src/net/http/httptest下。
其原理的话我也看了源码研究了下,这里大致说下,它有一个ResponseRecorder结构体,它实现了http.ResponseWriter,同时它自身又包含了http.Response,前者是server端维度下的response,后者是client端维度下的response,也就是说,ResponseRecorder同时实现了server和client的功能。
好接下来看看如何使用httptest
httptest构建
// 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
}
// PostForm 根据特定请求uri和参数param,以表单形式传递参数,发起post请求返回响应
func PostForm(uri string, param url.Values, router *gin.Engine) []byte {
// 构造post请求
req := httptest.NewRequest("POST", uri, strings.NewReader(param.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 初始化响应
w := httptest.NewRecorder()
// 调用相应handler接口
router.ServeHTTP(w, req)
// 提取响应
result := w.Result()
defer result.Body.Close()
// 读取响应body
body, _ := ioutil.ReadAll(result.Body)
return body
}
在gin中注册路由,并写了两个接口样例
regAdmin := router.Group("/quote")
regAdmin.GET("/ge", Ge)
regAdmin.POST("/pos", Pos)
type TestForPost struct {
Strname string `form:"strname" binding:"required"`
Number int `form:"number" binding:"required"`
}
// Get接口
func Ge(c *gin.Context) {
c.String(http.StatusOK, "success")
}
// Post接口
func Pos(c *gin.Context) {
req := &TestForPost{}
if err := c.ShouldBindWith(req, binding.Form); err != nil {
fmt.Println("bind error",err)
c.JSON(http.StatusOK, gin.H{
"errno":"1",
"errmsg":"参数不匹配",
"data":"",
})
return
}
c.JSON(http.StatusOK, gin.H{
"errno":"0",
"errmsg":"Null",
"data":req,
})
}
写单元测试
type Response struct {
Errno string `json:"errno"`
Errmsg string `json:"errmsg"`
Data TestForPost `json:"data"`
}
func TestOnGetStringRequest(t *testing.T) {
// 初始化请求地址
url:="/quote/ge"
// 发起Get请求
body := Get(url, router)
// 判断响应是否与预期一致
if string(body) != "success" {
t.Errorf("响应字符串不符,body:%v\n",string(body))
}
convey.Convey("测试GET接口", t, func() {
convey.So(string(body), convey.ShouldEqual, "success")
})
}
func TestOnPostRequestForForm(t *testing.T) {
// 初始化请求地址和请求参数
uri := "/quote/pos"
param := url.Values{
"strname":{"test"},
"number":{"1"},
}
// 发起post请求,以表单形式传递参数
body := PostForm2(uri, param, router)
//body := PostForm3(uri, param, router)
// 解析响应,判断响应是否与预期一致
response := &Response{}
if err := json.Unmarshal(body, response); err != nil {
t.Errorf("解析响应出错,err:%v\n",err)
}
t.Log(response.Data)
if response.Data.Strname != "test" {
t.Errorf("响应数据不符,errmsg:%v, data:%v\n",response.Errmsg,response.Data.Strname)
}
convey.Convey("测试POST接口", t, func() {
convey.So(response.Data.Strname, convey.ShouldEqual, "test")
})
}
完成