前言
前幾話主要講解關(guān)于使用golang進(jìn)行單元測(cè)試,在單元測(cè)試的上一層就是接口測(cè)試,本節(jié)主要講使用golang進(jìn)行接口測(cè)試,其中主要以http協(xié)議的接口測(cè)試來(lái)講解
golang中的http請(qǐng)求
golang中擁有一個(gè)原生的http依賴庫(kù):net/http,http服務(wù)器的建立還是http客戶端的開(kāi)發(fā),都會(huì)使用到這個(gè)依賴庫(kù),這里主要講解時(shí)client部分,作為請(qǐng)求發(fā)起方應(yīng)用于日常的接口測(cè)試,例示代碼如下:
get請(qǐng)求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { //模擬一個(gè)get提交請(qǐng)求 resp, err := http.Get("http://127.0.0.1:12345/checkon") if err != nil { panic(err) } defer resp.Body.Close() //關(guān)閉連接 body, err := ioutil.ReadAll(resp.Body) //讀取body的內(nèi)容 fmt.Println(string(body)) } |
返回結(jié)果
1
2
3
4
5
6
7
|
E:\go_project>go run testget.go { "code": 200, "data": "", "msg": "online", "state": "success" } |
post請(qǐng)求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main import ( "fmt" "io/ioutil" "net/http" "strings" ) func main() { //模擬一個(gè)post提交請(qǐng)求 resp, err := http.Post("http://www.baidu.com", "application/x-www-form-urlencoded", strings.NewReader("id=1")) if err != nil { panic(err) } //關(guān)閉連接 defer resp.Body.Close() //讀取報(bào)文中所有內(nèi)容 body, err := ioutil.ReadAll(resp.Body) //輸出內(nèi)容 fmt.Println(string(body)) } |
上面的post請(qǐng)求以form的方式,最后會(huì)返回一個(gè)頁(yè)面
這里說(shuō)明一下以下這行代碼
1
|
defer resp.Body.Close() |
首先是defer, Go的defer語(yǔ)句用來(lái)調(diào)度一個(gè)函數(shù)調(diào)用(被延期的函數(shù)),使其在執(zhí)行defer的函數(shù)即將返回之前才被運(yùn)行,被延期執(zhí)行的函數(shù),它的參數(shù)(包括接受者)實(shí)在defer執(zhí)行的時(shí)候被求值的,而不是在調(diào)用執(zhí)行的時(shí)候。也就是說(shuō)被延期執(zhí)行的函數(shù)的參數(shù)是按正常順序被求值的,簡(jiǎn)單理解為,無(wú)論defer對(duì)應(yīng)的代碼行放在代碼段的哪個(gè)位置,defer是在return前執(zhí)行的代碼行,但defer代碼行中的參數(shù)是需要先聲明再調(diào)用的,對(duì)應(yīng)響應(yīng)中的處理,golang的Response.Body需要被關(guān)閉的,body實(shí)際上是一個(gè)嵌套了多層的net.TCPConn:
- bufio.Reader,這層嘗試將多次小的讀操作替換為一次大的讀操作,減少系統(tǒng)調(diào)用的次數(shù),提高性能;
- io.LimitedReader,tcp連接在讀取完body后不會(huì)關(guān)閉,繼續(xù)讀會(huì)導(dǎo)致阻塞,所以需要LimitedReader在body讀完后發(fā)出eof終止讀取;
- chunkedReader,解析chunked格式編碼(如果不是chunked略過(guò));
- bodyEOFSignal,在讀到eof,或者是提前關(guān)閉body時(shí)會(huì)對(duì)readLoop發(fā)出回收連接的通知;
- gzipReader,解析gzip壓縮(如果不是gizp壓縮略過(guò));
從上面可以看出如果body既沒(méi)有被完全讀取,也沒(méi)有被關(guān)閉,那么這次http事務(wù)就沒(méi)有完成,除非連接因超時(shí)終止了,否則相關(guān)資源無(wú)法被回收,所以需要我們進(jìn)行關(guān)閉連接的操作,這個(gè)是很多golang新手會(huì)忽略的一個(gè)點(diǎn),作為client端處理response的時(shí)候,body一定要close,否則會(huì)造成GC回收不到,繼而產(chǎn)生內(nèi)存泄露
帶json的post請(qǐng)求
我們大部分應(yīng)用到的restful接口都是用json格式的請(qǐng)求體,對(duì)應(yīng)的golang的http請(qǐng)求也會(huì)有相關(guān)的方式post json請(qǐng)求體
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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)) } |
執(zhí)行結(jié)果響應(yīng)
1
2
3
4
5
6
7
8
|
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" } |
對(duì)于常用的get和post請(qǐng)求基本上就以照上面的版本執(zhí)行,當(dāng)然我們現(xiàn)在需要做的是http接口的測(cè)試,那就需要引入測(cè)試框架進(jìn)行相關(guān)的校驗(yàn),本文先講解用之前提到的gocheck來(lái)進(jìn)行斷言
golang中的http接口測(cè)試
引入gocheck之后我們得到了以下的腳本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
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次套件開(kāi)始執(zhí)行" fmt.Println(str3) //c.Skip("Skip TestSutie") } func (s *MySuite) TearDownSuite(c *C) { str4:="第1次套件執(zhí)行完成" fmt.Println(str4) } func (s *MySuite) SetUpTest(c *C) { str1:="第"+strconv.Itoa(a)+"條用例開(kāi)始執(zhí)行" fmt.Println(str1) } func (s *MySuite) TearDownTest(c *C) { str2:="第"+strconv.Itoa(a)+"條用例執(zhí)行完成" 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() //關(guān)閉連接 body, err := ioutil.ReadAll(respget.Body) //讀取body的內(nèi)容 var gdat map[string]interface{} //定義map用于解析resp.body的內(nèi)容 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的內(nèi)容 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") //模擬成功的斷言 } |
最后的輸出內(nèi)容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
E:\go_project>go test -v gocheckhttp_test.go === RUN Test 第1次套件開(kāi)始執(zhí)行 第1條用例開(kāi)始執(zhí)行 map[code:200 data: msg:online state:success] 第1條用例執(zhí)行完成 ---------------------------------------------------------------------- FAIL: gocheckhttp_test.go:56: MySuite.TestHttpGet gocheckhttp_test.go:72: c.Assert(gmsg, Equals, "terrychow") ... obtained string = "online" ... expected string = "terrychow" 第2條用例開(kāi)始執(zhí)行 map[msg:terrychow state:success code:200 data:1] 第2條用例執(zhí)行完成 第1次套件執(zhí)行完成 OOPS: 1 passed, 1 FAILED --- FAIL: Test (0.02s) FAIL FAIL command-line-arguments 0.613s |
輸出的結(jié)果符合預(yù)期,這也是比較基本的http接口測(cè)試
小結(jié)
就上文來(lái)說(shuō),我們基本可以通過(guò)本文掌握如何做http接口測(cè)試,其核心還是使用http依賴庫(kù)發(fā)出請(qǐng)求獲取響應(yīng),利用gocheck進(jìn)行斷言,當(dāng)然還可以用testing,下一節(jié)繼續(xù)講一下http接口測(cè)試,但會(huì)重點(diǎn)講專門做http接口測(cè)試的測(cè)試框架httpexpect以及用于mock的httptest,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://studygolang.com/articles/16742