当前位置 博文首页 > golang分层测试之http接口测试入门教程

    golang分层测试之http接口测试入门教程

    作者:周学习的名字被占了 时间:2021-07-02 18:32

    前言

    前几话主要讲解关于使用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,希望对大家的学习有所帮助,也希望大家多多支持站长博客。

    jsjbwy