前言
- 前文主要讲解关于使用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进行压测,希望大家继续关注
有疑问加站长微信联系(非本文作者)