golang分層測試之http介面測試入門
摘要:
前言
前幾話主要講解關於使用golang進行單元測試,在單元測試的上一層就是介面測試,本節主要講使用golang進行介面測試,其中主要以http協議的介面測試來講解
golang中的http請求
golang中擁有一個原生的http依賴庫:net/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 === RUNTest 第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 FAILcommand-line-arguments0.613s
- 輸出的結果符合預期,這也是比較基本的http介面測試
小結
- 就上文來說,我們基本可以通過本文掌握如何做http介面測試,其核心還是使用http依賴庫發出請求獲取響應,利用gocheck進行斷言,當然還可以用testing,下一節繼續講一下http介面測試,但會重點講專門做http介面測試的測試框架httpexpect以及用於mock的httptest,請繼續關注,謝謝