忽视httptest测试工具包
Go语言标准库提供了一些用于测试的工具包,常见的问题是有些开发者不知道这些工具包,并试图重新造轮子或依赖其他不太方便的处理方法。本文将深入研究httptest工具包,它可以帮助我们方便测试HTTP程序.
httptest
httptest包可以辅助我们对HTTP的客户端和服务器程序进行测试,下面学习如何使用httptest进行测试。
首先,来看看编写HTTP服务器程序时,如何使用httptest包辅助我们进行测试。下面的Handler程序执行一些基本操作:设置http header,写入body数据并返回特定的状态码。为了突出核心逻辑,省略了错误处理。
代码语言:javascript复制func Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-API-VERSION", "1.0")
b, _ := io.ReadAll(r.Body)
_, _ = w.Write(append([]byte("hello "), b...))
w.WriteHeader(http.StatusCreated)
}
HTTP处理程序接收两个参数:*http.Request和http.ResponseWriter,分别表示请求信息和写入响应。httptest包提供了创建这两个参数类型对象的构造方法。可以使用httptest.NewRequest创建一个 *http.Request对象,设置HTTP请求方法(GET/POST/PUT等)、请求的URL和正文body内容。对于响应,可以使用httptest.NewRecorder来记录程序处理程序中发生的变化。下面使用httptest包对上面的Handler进行单元测试,实现代码如下。
代码语言:javascript复制func TestHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost",
strings.NewReader("foo"))
w := httptest.NewRecorder()
Handler(w, req)
if got := w.Result().Header.Get("X-API-VERSION"); got != "1.0" {
t.Errorf("api version: expected 1.0, got %s", got)
}
body, _ := ioutil.ReadAll(w.Body)
if got := string(body); got != "hello foo" {
t.Errorf("body: expected hello foo, got %s", got)
}
if http.StatusOK != w.Result().StatusCode {
t.FailNow()
}
}
使用httptest测试处理程序不会测试传输层,测试的重点是业务逻辑处理,直接使用请求和记录响应的方式调用处理程序,即在本地模拟整个业务处理,不关心网络传输部分。然后,获取记录响应中的内容,判断验证header、正文和状态码等内容是否正确。
下面看看如何使用httptest包辅助我们对客户端程序进行单元测试。先来编写一个 HTTP 客户端程序,该程序请求获取从一个坐标点移动到另一个坐标点的所需要的时间。实现逻辑就是对提供的url发起HTTP POST请求,然后解析返回的内容,返回所用时间。
代码语言:javascript复制func (c DurationClient) GetDuration(url string,
lat1, lng1, lat2, lng2 float64) (
time.Duration, error) {
resp, err := c.client.Post(
url, "application/json",
buildRequestBody(lat1, lng1, lat2, lng2),
)
if err != nil {
return 0, err
}
return parseResponseBody(resp.Body)
}
怎么对上面的函数进行单元测试呢?一种处理方法是使用Docker启动一个模拟的服务器,返回预先注册的响应,但是这种方法比较笨重,执行起来麻烦。另一种处理方法是使用http.NewServer基于提供的处理程序创建本地HTTP服务器,然后调用GetDuration并对返回的结果进行断言。像下面这样,通过httptest.NewServer创建了一个返回持续时间为314秒的静态处理程序的服务器,在调用GetDuration时,传入本地服务器的URL(srv.URL), 将客户端请求与预定的本地处理程序关联起来,返回预定程序的返回值。
代码语言:javascript复制func TestDurationClientGet(t *testing.T) {
srv := httptest.NewServer(
http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"duration": 314}`))
},
),
)
defer srv.Close()
client := NewDurationClient()
duration, err :=
client.GetDuration(srv.URL, 51.551261, -0.1221146, 51.57, -0.13)
if err != nil {
t.Fatal(err)
}
if duration != 314*time.Second {
t.Errorf("expected 314 seconds, got %v", duration)
}
}
除了使用http.NewServer创建普通的服务器外,还可以使用http.NewTLSServer创建带有TLS的服务器,以及使用httptest.NewUnstartedServer 创建一个未启动的服务器,以便我们可以延迟启动它。
httptest对我们测试HTTP应用程序能够提供非常大的帮助,无论是编写服务器还是客户端程序,它都可以帮助我们进行高效的测试,所以在测试HTTP程序时,不要忽视httptest包。