httptest HTTP 测试辅助工具
Go 标准库 提供了 httptest 包 用于进行 http Web 开发测试

1.1. 简单的 Web 应用
构建 简单的 Web 应用 数据保存在内存 没有考虑并发问题

// 保存 Topic 没有考虑并发问题
var TopicCache = make([]*Topic, 0, 16)
type Topic struct {
  Id        int       `json:"id"`
  Title     string    `json:"title"`
  Content   string    `json:"content"`
  CreatedAt time.Time `json:"created_at"`
}
是通过 http 包来实现一个 Web 应用
func main() {
  http.HandleFunc("/topic/", handleRequest)
  http.ListenAndServe(":2017", nil)
}
/topic/ 开头的请求都交由 handleRequest 处理 它根据不同的 Method 执行相应的增删改查 详细代码可以查看 server.go
go run server.go data.go
通过 curl 进行简单的测试
增 curl -i -X POST http://localhost:2017/topic/ -H 'content-type: application/json' -d '{"title":"The Go Standard Library","content":"It contains many packages."}'
查 curl -i -X GET http://localhost:2017/topic/1
改 curl -i -X PUT http://localhost:2017/topic/1 -H 'content-type: application/json' -d '{"title":"The Go Standard Library By Example","content":"It contains many packages, enjoying it."}'
删 curl -i -X DELETE http://localhost:2017/topic/1

1.2. 通过 httptest 进行测试
通过 curl 对  Web 应用的接口进行了测试  通过 httptest 进行测试
先测试创建帖子 也就是测试 handlePost 函数
func TestHandlePost(t *testing.T) {
  mux := http.NewServeMux()
  mux.HandleFunc("/topic/", handleRequest)
  reader := strings.NewReader(`{"title":"The Go Standard Library","content":"It contains many packages."}`)
  r, _ := http.NewRequest(http.MethodPost, "/topic/", reader)
  w := httptest.NewRecorder()
  mux.ServeHTTP(w, r)
  resp := w.Result()
  if resp.StatusCode != http.StatusOK {
      t.Errorf("Response code is %v", resp.StatusCode)
  }
}
首先跟待测试代码一样 配置上路由 对 /topic/ 的请求都交由 handleRequest 处理
mux := http.NewServeMux()
mux.HandleFunc("/topic/", handleRequest)
因为 handlePost 的签名是 func handlePost(w http.ResponseWriter, r *http.Request) error
为了测试它 必须创建 http.ResponseWriter 和 http.Request 的实例
接下来的代码就是创建一个 http.Request 实例 和 一个 http.ReponseWriter 的实例
关键是 httptest 为提供了一个 http.ReponseWriter 接口的实现结构 httptest.ReponseRecorder 通过它可以得到一个 http.ReponseWriter
reader := strings.NewReader(`{"title":"The Go Standard Library","content":"It contains many packages."}`)
r, _ := http.NewRequest(http.MethodPost, "/topic/", reader)
w := httptest.NewRecorder()
准备好之后 可以测试目标函数了 这里 没有直接调用 handlePost(w, r) 而是调用 mux.ServeHTTP(w, r) 实际上这里直接调用 handlePost(w, r) 也是可以的 但调用 mux.ServeHTTP(w, r) 更完整的测试了整个流程 mux.ServeHTTP(w, r) 最终会调用 handlePost(w, r)
最后 通过 go test -v 运行测试
查 改和删帖子的接口测试代码类似 比如 handleGet 的测试代码如下
func TestHandleGet(t *testing.T) {
  mux := http.NewServeMux()
  mux.HandleFunc("/topic/", handleRequest)
  r, _ := http.NewRequest(http.MethodGet, "/topic/1", nil)
  w := httptest.NewRecorder()
  mux.ServeHTTP(w, r)
  resp := w.Result()
  if resp.StatusCode != http.StatusOK {
      t.Errorf("Response code is %v", resp.StatusCode)
  }
  topic := new(Topic)
  json.Unmarshal(w.Body.Bytes(), topic)
  if topic.Id != 1 {
      t.Errorf("Cannot get topic")
  }
}
因为数据没有落地存储 为了保证后面的测试正常 请将 TestHandlePost 放在最前面

1.3. 测试代码改进
细心的朋友应该会发现 上面的测试代码有重复 比如
mux := http.NewServeMux()
mux.HandleFunc("/topic/", handleRequest)
还有 w := httptest.NewRecorder()
这正好是前面学习的 setup 可以做的事情 因此可以使用 TestMain 来做重构
var w *httptest.ResponseRecorder
func TestMain(m *testing.M) {
  http.DefaultServeMux.HandleFunc("/topic/", handleRequest)

  w = httptest.NewRecorder()

  os.Exit(m.Run())
}