Go中的微服务:REST APIs

68 阅读2分钟

实施测试

上次我们实现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)
			}
		})
	}
}

这个想法是。

  1. 定义一个output 结构类型,意在代表响应中返回的数据,这意味着状态代码、响应和用于解封的目标类型。
  2. 我喜欢定义一个辅助测试函数,比如说 assertResponse,将主体结果与预期的非遮蔽值进行比较。
  3. 测试表将使用通常的inputoutput 以及一个新的setup 函数定义测试,该函数将初始化我们的模拟类型。
  4. 最后,我们的实际测试将连接所有的点,调用初始化器,进行请求并比较结果。

总结

实现HTTP处理程序的测试只需要几个步骤,比实现处理程序更容易,而且在实践中应该不需要第三方包来完成我们想要确认的事情,如果有的话,也许使用一个平等的包,比如github.com/google/go-c…应该是绰绰有余的。