我们在写完接口之后都需要对接口进行测试, 在 golang 标准库中提供 httptest 包来辅助测试。
因为接口都是需要 IP 地址或域名来访问, httptest 包中默认定义了服务地址
const DefaultRemoteAddr = "1.2.3.4"
NewRequest 方法用来创建一个 http 的请求体。
方法定义:
func NewRequest(method, target string, body io.Reader) *http.Request
方法定义:
func NewRecorder() *ResponseRecorder
NewRecorder 方法用来创建 http 的响应体。返回的类型是 *httptest.ResponseRecorder , 包含接口返回信息, 等价于 http.ResponseWriter。
ResponseRecorder 类型定义:
type ResponseRecorder struct {
// http 响应码.
Code int
// 头部信息
HeaderMap http.Header
// 返回的 Body
Body *bytes.Buffer
// 是否调用 Flush 方法
Flushed bool
}
方法定义:
func NewServer(handler http.Handler) *Server
NewServer 方法用来创建和启动新的服务。同类的还有 NewTLSServer, 用来创建带 SSL 的服务。
type Server struct {
URL string // 服务地址
Listener net.Listener
// TLS 配置信息
TLS *tls.Config
Config *http.Server
}
请求接口定义:
func testAPI(w http.ResponseWriter, r *http.Request){}
测试方法定义:
func Test_testApi(t *testing.T) {
tests := []struct {
name string
}{
{
name: "test api",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(testAPI))
defer ts.Close()
params := struct{
"params" string
}{
"params": "paramsBody"
}
paramsByte, _ := json.Marshal(params)
resp, err := http.Post(ts.URL, "application/json", bytes.NewBuffer(paramsByte))
if err != nil {
t.Error(err)
}
defer resp.Body.Close()
t.Log(resp.StatusCode)
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
t.Error(string(body))
}
})
}
}
测试时通过 httptest.NewServer 创建一个 testAPI 接口的服务。然后通过 http.Post 方法来调用我们创建的服务, 达到接口测试时请求的目的。然后判断接口返回的信息是否正确。
上面一个例子很有用, 但是如何去发送一个真正的 http request 而不去真正的启动一个 http server(亦或者请求任意的 server)? 答案是使用 Go 的 httptest 包, 这个包可以非常简单的创建一个测试的 http server, 那么下面我们将展示一下完整的代码, 并解释一下整体的测试流程:
person.go:
package person
import (
"net/http"
"fmt"
"io/ioutil"
"encoding/json"
"github.com/astaxie/beego/logs"
)
const (
ADDRESS = "shanghai"
)
type Person struct {
Name string `json:"name"`
Address string `json:"address"`
Age int `json:"age"`
}
func GetInfo(api string) ([]Person, error) {
url := fmt.Sprintf("%s/person?addr=%s", api, ADDRESS)
resp, err := http.Get(url)
defer resp.Body.Close()
if err != nil {
return []Person{}, err
}
if resp.StatusCode != http.StatusOK {
return []Person{}, fmt.Errorf("get info didn’t respond 200 OK: %s", resp.Status)
}
bodyBytes, _ := ioutil.ReadAll(resp.Body)
personList := make([]Person,0)
err = json.Unmarshal(bodyBytes, &personList)
if err != nil {
logs.Error("decode data fail")
return []Person{}, fmt.Errorf("decode data fail")
}
return personList, nil
}
person_test.go:
package person
import (
"testing"
"net/http/httptest"
"net/http"
"fmt"
"encoding/json"
)
var personResponse = []Person{
{
Name : "wahaha",
Address : "shanghai",
Age : 20,
},
{
Name : "lebaishi",
Address : "shanghai",
Age : 10,
},
}
var personResponseBytes, _ = json.Marshal(personResponse)
func TestPublishWrongResponseStatus(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(personResponseBytes)
if r.Method != "GET"{
t.Errorf("Expected'GET'request, got'%s'", r.Method)
}
if r.URL.EscapedPath() != "/person" {
t.Errorf("Expected request to'/person', got'%s'", r.URL.EscapedPath())
}
r.ParseForm()
topic := r.Form.Get("addr")
if topic != "shanghai" {
t.Errorf("Expected request to have'addr=shanghai', got:'%s'", topic)
}
}))
defer ts.Close()
api := ts.URL
fmt.Println("url:", api)
resp, _ := GetInfo(api)
fmt.Println("reps:", resp)
}
解释一下:
httptest.NewServer
创建了一个测试的 http serverr *http.Request
, 写变量 (也就是返回值) 通过 w http.ResponseWriter
ts.URL
来获取请求的 URL(一般都是 <http://ip:port>
)r.Method
来获取请求的方法, 来测试判断我们的请求方法是否正确r.URL.EscapedPath()
, 本例中的请求路径就是 /person
r.ParseForm, r.Form.Get("addr")
w.WriteHeader(http.StatusOK)
w.Write(personResponseBytes)
, 注意 w.Write()
接收的参数是 []byte
, 因此需要将 object 对象列表通过 json.Marshal(personResponse)
转换成字节。You might be able to do what you need with net.Pipe which basically gives you both ends of a connection (think, after .Accept())
server, client := net.Pipe()
go func() {
// Do some stuff
server.Close()
}()
// Do some stuff
client.Close()