分享
  1. 首页
  2. 文章

基于golang gin框架的单元测试

Valiben · · 14769 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在用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,返回的响应将写入前面构造的响应中,通过解析响应,查看其中数据即可完成对接口的测试


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:Valiben

查看原文:基于golang gin框架的单元测试

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
14769 次点击 ∙ 1 赞
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏