golang分层测试之http接口测试入门教程
前言
前几话主要讲解关于使用golang进行单元测试,在单元测试的上一层就是接口测试,本节主要讲使用golang进行接口测试,其中主要以http协议的接口测试来讲解
golang中的http请求
golang中拥有一个原生的http依赖库:net/http,http服务器的建立还是http客户端的开发,都会使用到这个依赖库,这里主要讲解时client部分,作为请求发起方应用于日常的接口测试,例示代码如下:
get请求
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { //模拟一个get提交请求 resp, err := http.get("http://127.0.0.1:12345/checkon") if err != nil { panic(err) } defer resp.body.close() //关闭连接 body, err := ioutil.readall(resp.body) //读取body的内容 fmt.println(string(body)) }
返回结果
e:\go_project>go run testget.go { "code": 200, "data": "", "msg": "online", "state": "success" }
post请求:
package main import ( "fmt" "io/ioutil" "net/http" "strings" ) func main() { //模拟一个post提交请求 resp, err := http.post("http://www.baidu.com", "application/x-www-form-urlencoded", strings.newreader("id=1")) if err != nil { panic(err) } //关闭连接 defer resp.body.close() //读取报文中所有内容 body, err := ioutil.readall(resp.body) //输出内容 fmt.println(string(body)) }
上面的post请求以form的方式,最后会返回一个页面
这里说明一下以下这行代码
defer resp.body.close()
首先是defer, go的defer语句用来调度一个函数调用(被延期的函数),使其在执行defer的函数即将返回之前才被运行,被延期执行的函数,它的参数(包括接受者)实在defer执行的时候被求值的,而不是在调用执行的时候。也就是说被延期执行的函数的参数是按正常顺序被求值的,简单理解为,无论defer对应的代码行放在代码段的哪个位置,defer是在return前执行的代码行,但defer代码行中的参数是需要先声明再调用的,对应响应中的处理,golang的response.body需要被关闭的,body实际上是一个嵌套了多层的net.tcpconn:
- bufio.reader,这层尝试将多次小的读操作替换为一次大的读操作,减少系统调用的次数,提高性能;
- io.limitedreader,tcp连接在读取完body后不会关闭,继续读会导致阻塞,所以需要limitedreader在body读完后发出eof终止读取;
- chunkedreader,解析chunked格式编码(如果不是chunked略过);
- bodyeofsignal,在读到eof,或者是提前关闭body时会对readloop发出回收连接的通知;
- gzipreader,解析gzip压缩(如果不是gizp压缩略过);
从上面可以看出如果body既没有被完全读取,也没有被关闭,那么这次http事务就没有完成,除非连接因超时终止了,否则相关资源无法被回收,所以需要我们进行关闭连接的操作,这个是很多golang新手会忽略的一个点,作为client端处理response的时候,body一定要close,否则会造成gc回收不到,继而产生内存泄露
带json的post请求
我们大部分应用到的restful接口都是用json格式的请求体,对应的golang的http请求也会有相关的方式post json请求体
package main import ( "fmt" "io/ioutil" "net/http" "bytes" "encoding/json" ) type httpdata struct { flag int `json:"flag"` msg string `json:"msg"` } func main() { url := "http://127.0.0.1:12345/postdata" contenttype := "application/json;charset=utf-8" var httpdata httpdata httpdata.flag = 1 httpdata.msg = "terrychow" b ,err := json.marshal(httpdata) if err != nil { fmt.println("json format error:", err) return } body := bytes.newbuffer(b) resp, err := http.post(url, contenttype, body) if err != nil { fmt.println("post failed:", err) return } defer resp.body.close() content, err := ioutil.readall(resp.body) if err != nil { fmt.println("read failed:", err) return } fmt.println("header:", resp.header) fmt.println("content:", string(content)) }
执行结果响应
e:\go_project>go run gohttptest.go header: map[content-type:[application/json] content-length:[78] server:[werkzeug/0.14.1 python/2.7.15] date:[thu, 06 dec 2018 16:35:11 gmt]] content: { "code": 200, "data": 1, "msg": "terrychow", "state": "success" }
对于常用的get和post请求基本上就以照上面的版本执行,当然我们现在需要做的是http接口的测试,那就需要引入测试框架进行相关的校验,本文先讲解用之前提到的gocheck来进行断言
golang中的http接口测试
引入gocheck之后我们得到了以下的脚本:
package hello_test import ( "testing" "fmt" "strconv" "io/ioutil" "net/http" "bytes" "encoding/json" . "gopkg.in/check.v1" ) var a int =1 // hook up gocheck into the "go test" runner. func test(t *testing.t) { testingt(t) } type mysuite struct{} type httpdata struct { flag int `json:"flag"` msg string `json:"msg"` } var _ = suite(&mysuite{}) var testurl string ="http://127.0.0.1:12345" func (s *mysuite) setupsuite(c *c) { str3:="第1次套件开始执行" fmt.println(str3) //c.skip("skip testsutie") } func (s *mysuite) teardownsuite(c *c) { str4:="第1次套件执行完成" fmt.println(str4) } func (s *mysuite) setuptest(c *c) { str1:="第"+strconv.itoa(a)+"条用例开始执行" fmt.println(str1) } func (s *mysuite) teardowntest(c *c) { str2:="第"+strconv.itoa(a)+"条用例执行完成" fmt.println(str2) a=a+1 } func (s *mysuite) testhttpget(c *c) { geturl := fmt.sprintf("%v/checkon", testurl) respget, err := http.get(geturl) if err != nil { panic(err) } defer respget.body.close() //关闭连接 body, err := ioutil.readall(respget.body) //读取body的内容 var gdat map[string]interface{} //定义map用于解析resp.body的内容 if err := json.unmarshal([]byte(string(body)), &gdat); err == nil { fmt.println(gdat) } else { fmt.println(err) } var gmsg=gdat["msg"] c.assert(gmsg, equals, "terrychow") //模拟失败的断言 } func (s *mysuite) testhttppost(c *c) { url := fmt.sprintf("%v/postdata", testurl) contenttype := "application/json;charset=utf-8" var httpdata httpdata httpdata.flag = 1 httpdata.msg = "terrychow" b ,err := json.marshal(httpdata) if err != nil { fmt.println("json format error:", err) return } body := bytes.newbuffer(b) resp, err := http.post(url, contenttype, body) if err != nil { fmt.println("post failed:", err) return } defer resp.body.close() content, err := ioutil.readall(resp.body) if err != nil { fmt.println("read failed:", err) return } var dat map[string]interface{} //定义map用于解析resp.body的内容 if err := json.unmarshal([]byte(string(content)), &dat); err == nil { fmt.println(dat) } else { fmt.println(err) } var msg=dat["msg"] c.assert(msg, equals, "terrychow") //模拟成功的断言 }
最后的输出内容:
e:\go_project>go test -v gocheckhttp_test.go === run test 第1次套件开始执行 第1条用例开始执行 map[code:200 data: msg:online state:success] 第1条用例执行完成 ---------------------------------------------------------------------- fail: gocheckhttp_test.go:56: mysuite.testhttpget gocheckhttp_test.go:72: c.assert(gmsg, equals, "terrychow") ... obtained string = "online" ... expected string = "terrychow" 第2条用例开始执行 map[msg:terrychow state:success code:200 data:1] 第2条用例执行完成 第1次套件执行完成 oops: 1 passed, 1 failed --- fail: test (0.02s) fail fail command-line-arguments 0.613s
输出的结果符合预期,这也是比较基本的http接口测试
小结
就上文来说,我们基本可以通过本文掌握如何做http接口测试,其核心还是使用http依赖库发出请求获取响应,利用gocheck进行断言,当然还可以用testing,下一节继续讲一下http接口测试,但会重点讲专门做http接口测试的测试框架httpexpect以及用于mock的httptest,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: PageOffice开发平台
下一篇: 如何吃香椿,这边告诉你