实施测试
上次我们实现HTTP处理程序时,提到了使用标准库时的一些难点,比如模式匹配或嵌套资源,另一方面,在Go中测试处理程序不需要第三方包,这是因为 net/http/httptest它已经包含在标准库中。
以最简单的形式使用net/http/httptest ,是这样的。
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>net/http/httptest</body></html>")
}
req := httptest.NewRequest("GET", "http://test.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
body, _ := io.ReadAll(resp.Body) // XXX: Explicitly ignoring errors
fmt.Printf("%d %s %s", resp.StatusCode, resp.Header.Get("Content-Type"), string(body))
}
其中,httptest.ResponseRecorder 在请求处理程序时充当写入者,这个参数最终将记录被写入的结果,它将允许我们测试我们正在努力实现。
结构化的HTTP测试
由于我们已经实现了我们的处理程序的方式,我喜欢在实现测试时遵循以下结构。
func Test<Name>_<HTTPVerb>(t *testing.T) {
t.Parallel()
type output struct {
expectedStatus int
expected interface{}
target interface{}
}
tests := []struct {
name string
setup func(*resttesting.Fake<Task>Service)
input []byte
output output
}{
{
// ...
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
router := mux.NewRouter()
svc := &resttesting.Fake<Task>Service{}
tt.setup(svc)
rest.New<Task>Handler(svc).Register(router)
//-
res := doRequest(router,
httptest.NewRequest(http.MethodPost, "/<name>", bytes.NewReader(tt.input)))
//-
assertResponse(t, res, test{tt.output.expected, tt.output.target})
if tt.output.expectedStatus != res.StatusCode {
t.Fatalf("expected code %d, actual %d", tt.output.expectedStatus, res.StatusCode)
}
})
}
}
这个想法是。
- 定义一个
output结构类型,意在代表响应中返回的数据,这意味着状态代码、响应和用于解封的目标类型。 - 我喜欢定义一个辅助测试函数,比如说
assertResponse,将主体结果与预期的非遮蔽值进行比较。 - 测试表将使用通常的
input和output以及一个新的setup函数定义测试,该函数将初始化我们的模拟类型。 - 最后,我们的实际测试将连接所有的点,调用初始化器,进行请求并比较结果。
总结
实现HTTP处理程序的测试只需要几个步骤,比实现处理程序更容易,而且在实践中应该不需要第三方包来完成我们想要确认的事情,如果有的话,也许使用一个平等的包,比如github.com/google/go-c…应该是绰绰有余的。