基于golang gin框架的单元测试
2017-12-05 00:00
4684 查看
摘要:本文主要分享一下怎么对运用了gin框架的webserver的handler接口进行单元测试
在用Gin框架编写了一个webserver之后,我们如果需要测试handlers接口函数的话,主要可以采用两种方式来进行。
第一种是部署webserver,然后通过浏览器或其他http请求模拟工具来手动模拟真实的http请求,发送http请求之后,解析返回的响应,查看响应是否符合预期;这种做法比较麻烦,而且测试结果不太可靠。
第二种是使用httptest结合testing来实现针对handlers接口函数的单元测试。
github项目地址:https://github.com/Valiben/gin_unit_test
下面以对四个接口做相应的单元测试为例,分享基于Gin的单元测试的一些方法:
//OnGetStringRequest返回success字符串的接口
funcOnGetStringRequest(c*gin.Context){
c.String(http.StatusOK,"success")
}
OnPracticeRequest:
//OnPracticeRequest返回practice.html页面的接口
funcOnPracticeRequest(c*gin.Context){
c.HTML(http.StatusOK,"practice.html",gin.H{})
}
OnLoginRequestForForm:
//OnLoginRequestForForm以表单形式传递参数的登录接口
funcOnLoginRequestForForm(c*gin.Context){
req:=&User{}
iferr:=c.ShouldBindWith(req,binding.Form);err!=nil{
log.Printf("err:%v",err)
c.JSON(http.StatusOK,gin.H{
&
7fe0
nbsp;"errno":"1",
"errmsg":"参数不匹配",
"data":"",
})
return
}
c.JSON(http.StatusOK,gin.H{
"errno":"0",
"errmsg":"",
"data":req,
})
}
OnLoginRequestForJson:
//OnLoginRequestForJson以Json形式传递参数的登录接口
funcOnLoginRequestForJson(c*gin.Context){
req:=&User{}
iferr:=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,
})
}
//承接前端传过来的json数据或form表单数据
typeUserstruct{
Usernamestring`form:"username"json:"username"binding:"required"`
Passwordstring`form:"password"json:"password"binding:"required"`
Ageint`form:"age"json:"age"binding:"required"`
}
LoginResponse结构体代码:
//LoginResponse登录接口的响应参数
typeLoginResponsestruct{
Errnostring`json:"errno"`
Errmsgstring`json:"errmsg"`
DataUser`json:"data"`
}
调用的工具函数:
//ParseToStr将map中的键值对输出成querystring形式
funcParseToStr(mpmap[string]string)string{
values:=""
forkey,val:=rangemp{
values+="&"+key+"="+val
}
temp:=values[1:]
values="?"+temp
returnvalues
}
//初始化路由
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中如下图所示:
在handler中以下面这样的形式获取数据库连接等中间件服务:
funcFormRequest(methodstring,uristring,parammap[string]string,router*gin.Engine)[]byte{
//构造method类型的请求,表单数据以querystring的形式加在uri之后
req:=httptest.NewRequest(method,uri+ParseToStr(param),nil)
//初始化响应
w:=httptest.NewRecorder()
//调用相应handler接口
router.ServeHTTP(w,req)
//提取响应
result:=w.Result()
deferresult.Body.Close()
//读取响应body
body,_:=ioutil.ReadAll(result.Body)
returnbody
}
funcJsonRequest(methodstring,uristring,paraminterface{},router*gin.Engine)[]byte{
//将参数转化为json比特流
jsonByte,_:=json.Marshal(param)
//构造method类型请求,json数据以请求body的形式传递
req:=httptest.NewRequest(method,uri,bytes.NewReader(jsonByte))
//初始化响应
w:=httptest.NewRecorder()
//调用相应的handler接口
router.ServeHTTP(w,req)
//提取响应
result:=w.Result()
deferresult.Body.Close()
//读取响应body
body,_:=ioutil.ReadAll(result.Body)
returnbody
}
//TestOnGetStringRequest测试以Get方式获取一段字符串的接口
funcTestOnGetStringRequest(t*testing.T){
//初始化请求地址
uri:="/getString"
//发起Get请求
body:=Get(uri,router)
fmt.Printf("response:%v\n",string(body))
//判断响应是否与预期一致
ifstring(body)!="success"{
t.Errorf("响应字符串不符,body:%v\n",string(body))
}
}
//TestOnPracticeRequest测试以Get方式获取practice.html页面的接口
funcTestOnPracticeRequest(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)
ifhtmlStr!=string(body){
t.Errorf("响应数据不符,body:%v\n",string(body))
}
}
//TestOnLoginRequestForForm测试以表单形式传递参数的登录接口
funcTestOnLoginRequestForForm(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{}
iferr:=json.Unmarshal(body,response);err!=nil{
t.Errorf("解析响应出错,err:%v\n",err)
}
ifresponse.Data.Username!="valiben"{
t.Errorf("响应数据不符,username:%v\n",response.Data.Username)
}
}
//TestOnLoginRequestForJson测试以Json形式传递参数的登录接口
funcTestOnLoginRequestForJson(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{}
iferr:=json.Unmarshal(body,response);err!=nil{
t.Errorf("解析响应出错,err:%v\n",err)
}
ifresponse.Data.Username!="valiben"{
t.Errorf("响应数据不符,username:%v\n",response.Data.Username)
}
}
在用Gin框架编写了一个webserver之后,我们如果需要测试handlers接口函数的话,主要可以采用两种方式来进行。
第一种是部署webserver,然后通过浏览器或其他http请求模拟工具来手动模拟真实的http请求,发送http请求之后,解析返回的响应,查看响应是否符合预期;这种做法比较麻烦,而且测试结果不太可靠。
第二种是使用httptest结合testing来实现针对handlers接口函数的单元测试。
github项目地址:
下面以对四个接口做相应的单元测试为例,分享基于Gin的单元测试的一些方法:
接口名称 | 请求地址 | 请求类型 | 响应数据类型 | 响应数据 |
OnGetStringRequest | /getString | get | string | success |
OnPracticeRequest | /practice | get | html页面 | practice.html |
OnLoginRequestForForm | /loginForm | post | json | |
OnLoginRequestForJson | /loginJson | post | json |
一、示例接口代码:
OnGetStringRequest:OnPracticeRequest:
OnLoginRequestForForm:
OnLoginRequestForJson:
二、调用的一些结构体和工具函数:
User结构体代码:LoginResponse结构体代码:
调用的工具函数:
三、单元测试编写步骤:
1.初始化路由
funcinit(){//初始化路由
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中如下图所示:
在handler中以下面这样的形式获取数据库连接等中间件服务:
2.包装构造http请求的函数(以便测试函数直接调用发起不同种类的http请求)
2.1构造以form表单形式传递参数的请求
//FormRequest根据特定请求uri和参数param,以表单形式传递参数,发起特定类型的请求,返回响应funcFormRequest(methodstring,uristring,parammap[string]string,router*gin.Engine)[]byte{
//构造method类型的请求,表单数据以querystring的形式加在uri之后
req:=httptest.NewRequest(method,uri+ParseToStr(param),nil)
//初始化响应
w:=httptest.NewRecorder()
//调用相应handler接口
router.ServeHTTP(w,req)
//提取响应
result:=w.Result()
deferresult.Body.Close()
//读取响应body
body,_:=ioutil.ReadAll(result.Body)
returnbody
}
2.2构造以Json形式传递参数的请求
//JsonRequest根据特定请求uri和参数param,以Json形式传递参数,发起特定类型的请求,返回响应funcJsonRequest(methodstring,uristring,paraminterface{},router*gin.Engine)[]byte{
//将参数转化为json比特流
jsonByte,_:=json.Marshal(param)
//构造method类型请求,json数据以请求body的形式传递
req:=httptest.NewRequest(method,uri,bytes.NewReader(jsonByte))
//初始化响应
w:=httptest.NewRecorder()
//调用相应的handler接口
router.ServeHTTP(w,req)
//提取响应
result:=w.Result()
deferresult.Body.Close()
//读取响应body
body,_:=ioutil.ReadAll(result.Body)
returnbody
}
3.编写测试函数
3.1针对OnGetStringRequest接口的测试函数
3.2针对OnPracticeRequest接口的测试函数
funcTestOnPracticeRequest(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)
ifhtmlStr!=string(body){
t.Errorf("响应数据不符,body:%v\n",string(body))
}
}
3.3针对OnLoginRequestForForm接口的测试函数
funcTestOnLoginRequestForForm(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{}
iferr:=json.Unmarshal(body,response);err!=nil{
t.Errorf("解析响应出错,err:%v\n",err)
}
ifresponse.Data.Username!="valiben"{
t.Errorf("响应数据不符,username:%v\n",response.Data.Username)
}
}
3.4针对OnLoginRequestForJson接口的测试函数
funcTestOnLoginRequestForJson(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{}
iferr:=json.Unmarshal(body,response);err!=nil{
t.Errorf("解析响应出错,err:%v\n",err)
}
ifresponse.Data.Username!="valiben"{
t.Errorf("响应数据不符,username:%v\n",response.Data.Username)
}
}
4.运行单元测试,查看测试结果
执行gotest./运行测试代码,测试结果如下四、总结
基于Gin的单元测试的主要步骤就是先要初始化路由,设定handler接口函数拦截的http请求地址(不需要设定监听端口号);然后通过"net/http/httptest"包的NewRequest(method,targetstring,bodyio.Reader)方法构造request,第一个参数是请求类型“POST”“GET”之类,第二个参数是请求的URI地址(form表单的参数可以通过querystring的形式附在URI地址后面进行传递),第三个参数是请求的请求body内容(json数据等其他类型的数据可以加在这里进行传递);接着通过NewRecorder()函数构造响应,调用func(engine*Engine)ServeHTTP(whttp.ResponseWriter,req*http.Request)方法来调用请求处理的接口handlers,返回的响应将写入前面构造的响应中,通过解析响应,查看其中数据即可完成对接口的测试相关文章推荐
- golang实战使用gin+xorm搭建go语言web框架restgo详解1.1 go语言的困境
- golang实战使用gin+xorm搭建go语言web框架restgo详解8 关于模板
- 基于ssm框架对mybatis映射文件的多对一返回类型的junit4单元测试
- golang实战使用gin+xorm搭建go语言web框架restgo详解1.2 我要做什么
- golang实战使用gin+xorm搭建go语言web框架restgo详解9 session、日志、鉴权、验证码等
- 基于SpringBoot框架的单元测试和集成测试的区别和联系
- [原创]基于mock对象和JUnit框架简化Spring Web组件单元测试
- 基于mock对象和JUnit框架简化Spring Web组件单元测试
- 基于gtest和VS2008搭建单元测试框架
- golang实战使用gin+xorm搭建go语言web框架restgo详解2 框架基本架构
- [转]Go语言(Golang)的Web框架比较:gin VS echo
- Go语言(Golang)的web框架比较之:gin vs echo
- Golang 微框架 Gin 简介
- golang实战使用gin+xorm搭建go语言web框架restgo详解3 系统常用配置参数
- golang实战使用gin+xorm搭建go语言web框架restgo详解4 路由配置
- golang实战使用gin+xorm搭建go语言web框架restgo详解5 控制器C
- golang web框架 gin 中template模板内容实时更新原理
- 使用golang gin框架sessions时碰到的gob问题
- golang实战使用gin+xorm搭建go语言web框架restgo详解5.2 跳转和重定向
- 基于Visual Studio与Google Test的单元测试框架搭建方法