原文:How to test Go HTTPS Services
譯文為第一人稱視角:
在DNSimple的微服務(wù)中,在Go的文檔中很難找到描述有關(guān)如何測(cè)試HTTP(S)端服務(wù)的文檔。大部分的文章,我發(fā)現(xiàn)要么是不完整,要么就是缺少一些測(cè)試樣例,特別是有關(guān)集成測(cè)試。
以下是我寫的一些服務(wù)樣例。我希望這個(gè)分享,在你的下一個(gè)Go項(xiàng)目中會(huì)節(jié)省一些測(cè)試的時(shí)間。如果你需要一些靈感來擴(kuò)這個(gè)測(cè)試,請(qǐng)查閱我們的Go客戶端接口,它會(huì)影響域、域名服務(wù)器和證書接口的交互。
開始編寫測(cè)試代碼:
# microservice.go
package microservice
import (
"fmt"
"net/http"
)
func NewServer(port string) *http.Server {
addr := fmt.Sprintf(":%s", port)
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
return &http.Server{
Addr: addr,
Handler: mux,
}
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
這是一個(gè)簡(jiǎn)單版本的項(xiàng)目。我們將會(huì)設(shè)置一些暴露出去的函數(shù)來舉例這個(gè)服務(wù)并使用net/http包 和寫一些私有函數(shù),來處理這些接口的方法。
現(xiàn)在讓我們來看寫這個(gè)單元測(cè)。他關(guān)鍵的代碼部分是使用了httptest包:
# microservice_test.go
package microservice
import (
"bytes"
"crypto/tls"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
)
var (
port = "8080"
)
//
// Unit Tests
//
func TestHandler(t *testing.T) {
expected := []byte("Hello World")
req, err := http.NewRequest("GET", buildUrl("/"), nil)
if err != nil {
t.Fatal(err)
}
res := httptest.NewRecorder()
handler(res, req)
if res.Code != http.StatusOK {
t.Errorf("Response code was %v; want 200", res.Code)
}
if bytes.Compare(expected, res.Body.Bytes()) != 0 {
t.Errorf("Response body was '%v'; want '%v'", expected, res.Body)
}
}
func buildUrl(path string) string {
return urlFor("http", port, path)
}
func urlFor(scheme string, serverPort string, path string) string {
return scheme + "://localhost:" + serverPort + path
}
前面幾行設(shè)置了預(yù)設(shè)的主體和一個(gè)Get請(qǐng)求。它是Go的標(biāo)準(zhǔn)HTTP測(cè)試庫(kù)的一部分,實(shí)例化了一個(gè)新的*httptest.ResponseRecorder方法:
ResponseRecorder方法實(shí)現(xiàn)了http.ResponseWriter,這個(gè)記錄是為接下來的變化而進(jìn)行的測(cè)試。
有了這個(gè)對(duì)象,我們就可以直接調(diào)用程序處理,然后我們可以維護(hù)它,以至于這些返回的HTTP代碼和主體都是如預(yù)期中的一樣。
集成測(cè)試:
單元測(cè)試是非常好的,但是如果我們想包含SSL/TLS握手來測(cè)試微服務(wù)實(shí)際的HTTP(S)請(qǐng)求呢?
在開始之前,我們需要一個(gè)子簽名的測(cè)試證書:
go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,localhost --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
這個(gè)命令將會(huì)創(chuàng)建兩個(gè)文件:
? ls *.pem
cert.pem key.pem
現(xiàn)在開始寫我們的集成測(cè)試:
func TestHTTPSServer(t *testing.T) {
srv := NewServer(httpsPort)
go srv.ListenAndServeTLS("cert.pem", "key.pem")
defer srv.Close()
time.Sleep(100 * time.Millisecond)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
res, err := client.Get(buildSecureUrl("/"))
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("Response code was %v; want 200", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
expected := []byte("Hello World")
if bytes.Compare(expected, body) != 0 {
t.Errorf("Response body was '%v'; want '%v'", expected, body)
}
}
首先我們構(gòu)建服務(wù)器(srv),然后我們啟動(dòng)它,分離在一個(gè)線程中,否則這個(gè)進(jìn)程將會(huì)掛起在這一行。我們對(duì)這個(gè)進(jìn)程進(jìn)行了一些睡眠,好讓這個(gè)服務(wù)能夠啟動(dòng)。
現(xiàn)在,我們可以構(gòu)造這個(gè)客戶端是InsecureSkipVerify: true,就好像我們說的一樣客戶端不需要驗(yàn)證簽名就可以進(jìn)行SSl握手。請(qǐng)記住,我們的簽名不是由一個(gè)已知的CA機(jī)構(gòu)頒發(fā)的,而是來自于我們自己的計(jì)算機(jī)。
最后一個(gè)測(cè)試部分,我么可以正真的執(zhí)行一個(gè)HTTP請(qǐng)求來客戶端,和我們要維護(hù)的代碼以及響應(yīng)主體。
總結(jié):
善于閱讀Go的官方文檔,Go的標(biāo)準(zhǔn)庫(kù)擁有所有的測(cè)試工具。