原文:How to test Go HTTPS Services
译文为第一人称视角:
在DNSimple的微服务中,在Go的文档中很难找到描述有关如何测试HTTP(S)端服务的文档。大部分的文章,我发现要么是不完整,要么就是缺少一些测试样例,特别是有关集成测试。
以下是我写的一些服务样例。我希望这个分享,在你的下一个Go项目中会节省一些测试的时间。如果你需要一些灵感来扩这个测试,请查阅我们的Go客户端接口,它会影响域、域名服务器和证书接口的交互。
开始编写测试代码:
# 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")
}
这是一个简单版本的项目。我们将会设置一些暴露出去的函数来举例这个服务并使用net/http包 和写一些私有函数,来处理这些接口的方法。
现在让我们来看写这个单元测。他关键的代码部分是使用了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
}
前面几行设置了预设的主体和一个Get请求。它是Go的标准HTTP测试库的一部分,实例化了一个新的*httptest.ResponseRecorder方法:
ResponseRecorder方法实现了http.ResponseWriter,这个记录是为接下来的变化而进行的测试。
有了这个对象,我们就可以直接调用程序处理,然后我们可以维护它,以至于这些返回的HTTP代码和主体都是如预期中的一样。
集成测试:
单元测试是非常好的,但是如果我们想包含SSL/TLS握手来测试微服务实际的HTTP(S)请求呢?
在开始之前,我们需要一个子签名的测试证书:
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
这个命令将会创建两个文件:
➜ ls *.pem
cert.pem key.pem
现在开始写我们的集成测试:
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)
}
}
首先我们构建服务器(srv),然后我们启动它,分离在一个线程中,否则这个进程将会挂起在这一行。我们对这个进程进行了一些睡眠,好让这个服务能够启动。
现在,我们可以构造这个客户端是InsecureSkipVerify: true,就好像我们说的一样客户端不需要验证签名就可以进行SSl握手。请记住,我们的签名不是由一个已知的CA机构颁发的,而是来自于我们自己的计算机。
最后一个测试部分,我么可以正真的执行一个HTTP请求来客户端,和我们要维护的代码以及响应主体。
总结:
善于阅读Go的官方文档,Go的标准库拥有所有的测试工具。