Go语言中常见100问题-#88-1 Not using testing utility packages

2022-08-15 15:32:06 浏览数 (1)

忽视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包。

0 人点赞