golang分层测试之httpexpect+httptest测试应用

周学习的名字被占了 · · 927 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

  • 前文主要讲解关于使用golang的http+gocheck进行接口测试,测试的流程基本上都是请求,返回数据和检验数据,但按照上文基于gocheck的实现显示还不太完善,今天主要讲解一下如果利用httpexpect进行http的接口测试,以及在前后端分离的研发过程中,server端未实现时使用httptest作为mock服务器的应用过程

httpexpect

  • 先讲一下httpexpect,httpexpect是专门用于做http rest api接口测试的,轻量级的测试框架。其代码格式写法有些类似于node.js的mocha, 用go get获取
go get -u github.com/gavv/httpexpect
  • 安装完成之后既可以导入并编写测试代码
  • 示例代码
package example

import (
    "net/http"
    "testing"
    "github.com/gavv/httpexpect"
)


var testurl string ="http://127.0.0.1:12345"

func TestHttpGetPass(t *testing.T) {
    e:= httpexpect.New(t, testurl)  //创建一个httpexpect实例
    e.GET("/checkon").   //ge请求
        Expect().
        Status(http.StatusOK).   //判断请求是否200
        JSON().                 
        Object().               //json body实例化
        ContainsKey("msg").     //检验是否包括key
        ValueEqual("msg","online")    //对比key的value
}

func TestHttpGetFail(t *testing.T) {    
    e:= httpexpect.New(t, testurl)  //创建一个httpexpect实例
    e.GET("/checkon").   //ge请求
        Expect().
        Status(http.StatusOK).   //判断请求是否200
        JSON().                 
        Object().               //json body实例化
        ContainsKey("msg1").     //检验是否包括key,使用不存在的key
        ValueEqual("msg","online")    //对比key的value
}

func TestHttpPostPass(t *testing.T) {
    e:= httpexpect.New(t, testurl)  //创建一个httpexpect实例
    postdata:= map[string]interface{}{   //创建一个json变量
      "flag": 1,
      "msg":"terrychow",
      }
    contentType := "application/json;charset=utf-8"

    e.POST("/postdata").   //post 请求
    WithHeader("ContentType", contentType). //定义头信息
    WithJSON(postdata).  //传入json body
    Expect().
    Status(http.StatusOK). //判断请求是否200
    JSON().                 
    Object().               //json body实例化
    ContainsKey("msg"). //检验是否包括key
    ValueEqual("msg","terrychow")    //对比key的value

}

func TestHttpPostFail(t *testing.T) {
    e:= httpexpect.New(t, testurl)  //创建一个httpexpect实例
    postdata:= map[string]interface{}{   //创建一个json变量
      "flag": 1,
      "msg":"terrychow",
      }
    contentType := "application/json;charset=utf-8"

    e.POST("/postdata").   //post 请求
    WithHeader("ContentType", contentType). //定义头信息
    WithJSON(postdata).  //传入json body
    Expect().
    Status(http.StatusOK). //判断请求是否200
    JSON().                 
    Object().               //json body实例化
    ContainsKey("msg"). //检验是否包括key
    ValueEqual("msg","terryzhou")    //对比key的value,value不匹配

}
  • 运行结果如下:
E:\go_project>go test -v  httpexpect_test.go
=== RUN   TestHttpGetPass
--- PASS: TestHttpGetPass (0.01s)
    printer.go:25: GET http://127.0.0.1:12345/checkon
=== RUN   TestHttpGetFail
--- FAIL: TestHttpGetFail (0.00s)
    printer.go:25: GET http://127.0.0.1:12345/checkon
    reporter.go:23:
                Error Trace:    reporter.go:23
                                                        chain.go:21
                                                        object.go:162
                                                        httpexpect_test.go:37
                Error:
                                expected object containing key 'msg1', but got:
                                 {
                                   "code": 200,
                                   "data": "",
                                   "msg": "online",
                                   "state": "success"
                                 }
                Test:           TestHttpGetFail
=== RUN   TestHttpPostPass
--- PASS: TestHttpPostPass (0.01s)
    printer.go:25: POST http://127.0.0.1:12345/postdata
=== RUN   TestHttpPostFail
--- FAIL: TestHttpPostFail (0.00s)
    printer.go:25: POST http://127.0.0.1:12345/postdata
    reporter.go:23:
                Error Trace:    reporter.go:23
                                                        chain.go:21
                                                        object.go:255
                                                        httpexpect_test.go:83
                Error:
                                expected value for key 'msg' equal to:
                                 "terryzhou"

                                but got:
                                 "terrychow"

                                diff:
                                 (unavailable)
                Test:           TestHttpPostFail
FAIL
FAIL    command-line-arguments  0.568s
  • httpexpect用于测试的api非常丰富,包括过程中的json化,获取body字段等都比之前直接用http+gocheck要方便很多,当然也可以利用gocheck把这个测试的过程串联起来,形成标准化的测试用例集
package example

import (
    "net/http"
    "testing"
    "fmt"
    "strconv"
    "github.com/gavv/httpexpect"
    . "gopkg.in/check.v1"
)

var a int =1

type MySuite struct{}

var testurl string ="http://127.0.0.1:12345"

var _ = Suite(&MySuite{})

func Test(t *testing.T) { TestingT(t) }


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) TestHttpGetPass(c *C) { //使用check实例代替testing实例即可,check实例本身就是继承testing实例
    e:= httpexpect.New(c, testurl)  //创建一个httpexpect实例 //传入替换成check实例
    e.GET("/checkon").   //ge请求
        Expect().
        Status(http.StatusOK).   //判断请求是否200
        JSON().                 
        Object().               //json body实例化
        ContainsKey("msg").     //检验是否包括key
        ValueEqual("msg","online")    //对比key的value
}

func (s *MySuite) TestHttpGetFail(c *C) {    
    e:= httpexpect.New(c, testurl)  //创建一个httpexpect实例
    e.GET("/checkon").   //ge请求
        Expect().
        Status(http.StatusOK).   //判断请求是否200
        JSON().                 
        Object().               //json body实例化
        ContainsKey("msg1").     //检验是否包括key,使用不存在的key
        ValueEqual("msg","online")    //对比key的value
}

func (s *MySuite) TestHttpPostPass(c *C) {
    e:= httpexpect.New(c, testurl)  //创建一个httpexpect实例
    postdata:= map[string]interface{}{   //创建一个json变量
      "flag": 1,
      "msg":"terrychow",
      }
    contentType := "application/json;charset=utf-8"

    e.POST("/postdata").   //post 请求
    WithHeader("ContentType", contentType). //定义头信息
    WithJSON(postdata).  //传入json body
    Expect().
    Status(http.StatusOK). //判断请求是否200
    JSON().                 
    Object().               //json body实例化
    ContainsKey("msg"). //检验是否包括key
    ValueEqual("msg","terrychow")    //对比key的value

}

func (s *MySuite) TestHttpPostFail(c *C) {
    e:= httpexpect.New(c, testurl)  //创建一个httpexpect实例
    postdata:= map[string]interface{}{   //创建一个json变量
      "flag": 1,
      "msg":"terrychow",
      }
    contentType := "application/json;charset=utf-8"

    e.POST("/postdata").   //post 请求
    WithHeader("ContentType", contentType). //定义头信息
    WithJSON(postdata).  //传入json body
    Expect().
    Status(http.StatusOK). //判断请求是否200
    JSON().                 
    Object().               //json body实例化
    ContainsKey("msg"). //检验是否包括key
    ValueEqual("msg","terryzhou")    //对比key的value,value不匹配
}
  • 运行结果
$ go test expect_test.go
第1次套件开始执行
第1条用例开始执行
第1条用例执行完成

----------------------------------------------------------------------
FAIL: expect_test.go:58: MySuite.TestHttpGetFail

GET http://127.0.0.1:12345/checkon
expect_test.go:65:
    e.GET("/checkon"). //ge请求
                       Expect().
                       Status(http.StatusOK). //判断请求是否200
                       JSON().
                       Object().                   //json body实例化
                       ContainsKey("msg1").        //检验是否包括key,使用不存在的key
                       ValueEqual("msg", "online") //对比key的value
/Users/zhouweiqu/mygo/src/github.com/stretchr/testify/assert/assertions.go:260:
    t.Errorf("\n%s", ""+labeledOutput(content...))
... Error:
    Error Trace:    reporter.go:23
                                chain.go:21
                                object.go:162
                                expect_test.go:65
    Error:
                    expected object containing key 'msg1', but got:
                     {
                       "code": 200,
                       "data": "",
                       "msg": "online",
                       "state": "success"
                     }


第2条用例开始执行
第2条用例执行完成
第3条用例开始执行
第3条用例执行完成

----------------------------------------------------------------------
FAIL: expect_test.go:89: MySuite.TestHttpPostFail

POST http://127.0.0.1:12345/postdata
expect_test.go:105:
    e.POST("/postdata"). //post 请求
                         WithHeader("ContentType", contentType). //定义头信息
                         WithJSON(postdata).                     //传入json body
                         Expect().
                         Status(http.StatusOK). //判断请求是否200
                         JSON().
                         Object().                      //json body实例化
                         ContainsKey("msg").            //检验是否包括key
                         ValueEqual("msg", "terryzhou") //对比key的value,value不匹配
/Users/zhouweiqu/mygo/src/github.com/stretchr/testify/assert/assertions.go:260:
    t.Errorf("\n%s", ""+labeledOutput(content...))
... Error:
    Error Trace:    reporter.go:23
                                chain.go:21
                                object.go:255
                                expect_test.go:105
    Error:
                    expected value for key 'msg' equal to:
                     "terryzhou"

                    but got:
                     "terrychow"

                    diff:
                     (unavailable)


第4条用例开始执行
第4条用例执行完成
第1次套件执行完成
OOPS: 2 passed, 2 FAILED
--- FAIL: Test (0.01s)
FAIL
FAIL    command-line-arguments  0.037s
  • 建议初入门的同学可以使用本文的httpexpect+gocheck的方式做基本的接口测试,会比较简单入手,同时也好维护

httptest

  • httptest是golang自有的标准mock server依赖库,如前文所说,前后端分离的研发过程中,server端未实现时需要利用到mock,httptest就提供给我们这个功能,使用它不用真正的启动一个http server或者请求任意的http server
  • 假设现在有个场景,就是要获取学生的信息接口,由于一些问题服务器不可用,这时候就可以使用httptest来模拟响应
  • 被测接口代码:student.go
package student

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

const (
    NAME = "terrychow"
)

type Student struct {
    Name   string `json:"name"`
    Sex    string `json:"sex"`
    Class    string `json:"clase"`
    Number string `json:"number"`
}

func GetStudentInfo(api string) ([]Student, error) { //将要被mock的接口
    url := fmt.Sprintf("%s/student?name=%s", api, NAME)
    resp, err := http.Get(url)

    if err != nil {
        return []Student{}, err
    }

    if resp.StatusCode != http.StatusOK {
        return []Student{}, fmt.Errorf("Resp is didn't 200 OK:%s", resp.Status)
    }
    bodybytes, _ := ioutil.ReadAll(resp.Body)
    personList := make([]Student, 0)

    err = json.Unmarshal(bodybytes, &personList)

    if err != nil {
        fmt.Errorf("Decode data fail")
        return []Student{}, fmt.Errorf("Decode data fail")
    }
    return personList, nil
  • mock服务器代码:student_test.go
package student

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"
)

var StudentResp = []Student{
    {
        Name:    "terrychow",
        Sex:    "m",
        Class:    "001",
        Number: "00101",
    },
    {
        Name:    "terryzhou",
        Sex:    "m",
        Class:    "001",
        Number: "00102",
    },
    {
        Name:    "lilizhou",
        Sex:    "w",
        Class:    "002",
        Number: "00101",
    },
}
var StudentRespBytes, _ = json.Marshal(StudentResp)

func TestGetInfoFail(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusUnauthorized)
        w.Write(StudentRespBytes)

        if r.Method != "GET" {
            t.Errorf("Except 'Get' got '%s'", r.Method)
        }

        if r.URL.EscapedPath() != "/student" {
            t.Errorf("Except to path '/student',got '%s'", r.URL.EscapedPath())
        }
        r.ParseForm()
        stuname := r.Form.Get("name")
        if  stuname!= "terrychow" {
            t.Errorf("Except rquest to have 'name=terrychow',got '%s'", stuname)
        }
    }))
    defer ts.Close()
    api := ts.URL
    fmt.Printf("Url:%s\n", api)
    resp, err := GetStudentInfo(api)

    if err != nil {
        fmt.Println("ERR:", err)
    } else {
        fmt.Println("resp:", resp)
    }
}

func TestGetInfoOK(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write(StudentRespBytes)

        if r.Method != "GET" {
            t.Errorf("Except 'Get' got '%s'", r.Method)
        }

        if r.URL.EscapedPath() != "/student" {
            t.Errorf("Except to path '/student',got '%s'", r.URL.EscapedPath())
        }
        r.ParseForm()
        stuname := r.Form.Get("name")
        if stuname!= "terrychow" {
            t.Errorf("Except rquest to have 'name=terrychow',got '%s'", stuname)
        }
    }))

    defer ts.Close()
    api := ts.URL
    fmt.Printf("Url:%s\n", api)
    resp, err := GetStudentInfo(api)
    if err != nil {
        fmt.Println("ERR:", err)
    } else {
        fmt.Println("resp:", resp)
    }
}
  • 运行结果通过
go test student_test.go student.go
ok      command-line-arguments  (cached)
  • 假设调整一下里面的student.go的请求值
NAME = "terry"
  • 运行结果:
go test student_test.go student.go
Url:http://127.0.0.1:60989
ERR: Resp is didn't 200 OK:401 Unauthorized
--- FAIL: TestGetInfoFail (0.00s)
    student_test.go:48: Except rquest to have 'name=terrychow',got 'terry'
Url:http://127.0.0.1:60991
resp: [{terrychow m 001 00101} {terryzhou m 001 00102} {lilizhou w 002 00101}]
--- FAIL: TestGetInfoOK (0.00s)
    student_test.go:78: Except rquest to have 'name=terrychow',got 'terry'
FAIL
FAIL    command-line-arguments  0.021s
  • 这样就可以看到这个mock的过程,当然在mock服务器建立之前,我们为了可以检验一下mock是否可以用,可以使用httpexpect来检验
    使用
package student

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
    "github.com/gavv/httpexpect"
    . "gopkg.in/check.v1"
)

type MySuite struct{}

var _ = Suite(&MySuite{})

func Test(t *testing.T) { TestingT(t) }

var StudentResp= map[string]interface{}{  //创建一个json变量
        "Name":    "terrychow",
        "Sex":    "m",
        "Class":    "001",
        "Number": "00101",
    }


var StudentRespBytes, _ = json.Marshal(StudentResp)



func (s *MySuite) TestGetInfoFail(c *C) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        w.Write(StudentRespBytes)
    }))
    defer ts.Close()
    e := httpexpect.New(c, ts.URL)

    e.GET("/student?name=terrychow").
        Expect().
        Status(http.StatusOK).   //判断请求是否200
        JSON().                 
        Object().               //json body实例化
        ContainsKey("Name").     //检验是否包括key
        ValueEqual("Name","terryzhou")    //对比key的value
   
}

func (s *MySuite) TestGetInfoOK(c *C) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        w.Write(StudentRespBytes)
    }))
    defer ts.Close()
    e := httpexpect.New(c, ts.URL)

    e.GET("/student?name=terrychow").
        Expect().
        Status(http.StatusOK).   //判断请求是否200
        JSON().                 
        Object().               //json body实例化
        ContainsKey("Name").     //检验是否包括key
        ValueEqual("Name","terrychow")    //对比key的value
   
}
  • 其运行结果
go test student_test.go

----------------------------------------------------------------------
FAIL: student_test.go:30: MySuite.TestGetInfoFail

GET http://127.0.0.1:63777/student%3Fname=terrychow
student_test.go:45:
    e.GET("/student?name=terrychow").
        Expect().
        Status(http.StatusOK). //判断请求是否200
        JSON().
        Object().                       //json body实例化
        ContainsKey("Name").            //检验是否包括key
        ValueEqual("Name", "terryzhou") //对比key的value
/Users/zhouweiqu/mygo/src/github.com/stretchr/testify/assert/assertions.go:260:
    t.Errorf("\n%s", ""+labeledOutput(content...))
... Error:
    Error Trace:    reporter.go:23
                                chain.go:21
                                object.go:255
                                student_test.go:45
    Error:
                    expected value for key 'Name' equal to:
                     "terryzhou"

                    but got:
                     "terrychow"

                    diff:
                     (unavailable)


OOPS: 1 passed, 1 FAILED
--- FAIL: Test (0.01s)
FAIL
FAIL    command-line-arguments  0.024s
  • 这样httptest的mock server就能够检查是可用的

总结

  • 接口测试相关的篇章基本讲述完毕,通过这几篇文章希望大家对使用golang进行http接口测试有所了解,接下来就会开始将本次专题文章的重点,使用golang进行压测,希望大家继续关注

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

本文来自:简书

感谢作者:周学习的名字被占了

查看原文:golang分层测试之httpexpect+httptest测试应用

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

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