# httptest - HTTP 测试辅助工具 #
由于 Go 标准库的强大支持,Go 可以很容易的进行 Web 开发。为此,Go 标准库专门提供了 httptest 包专门用于进行 http Web 开发测试。
本节我们通过一个社区帖子的增删改查的例子来学习该包。
## 简单的 Web 应用
我们首先构建一个简单的 Web 应用。
为了简单起见,数据保存在内存,并且没有考虑并发问题。
```go
// 保存 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"`
}
```
对于 Topic 的增删改查代码很简单,可以查看[完整代码](code/src/chapter09/httptest/data.go)。
接下来,是通过 http 包来实现一个 Web 应用。
```go
func main() {
http.HandleFunc("/topic/", handleRequest)
http.ListenAndServe(":2017", nil)
}
...
```
`/topic/` 开头的请求都交由 `handleRequest` 处理,它根据不同的 `Method` 执行相应的增删改查,详细代码可以查看 [server.go](code/src/chapter09/httptest/server.go)。
准备好 Web 应用后,我们启动它。
> 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
## 通过 httptest 进行测试
上面,我们通过 curl 对我们的 Web 应用的接口进行了测试。现在,我们通过 httptest 进行测试。
我们先测试创建帖子,也就是测试 `handlePost` 函数。
```go
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 ` 处理。
```go
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`。
```go
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` 的测试代码如下:
```go
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` 放在最前面。*
## 测试代码改进
细心的朋友应该会发现,上面的测试代码有重复,比如:
```go
mux := http.NewServeMux()
mux.HandleFunc("/topic/", handleRequest)
```
还有:`w := httptest.NewRecorder()`。
这正好是前面学习的 `setup` 可以做的事情,因此可以使用 `TestMain` 来做重构。
```go
var w *httptest.ResponseRecorder
func TestMain(m *testing.M) {
http.DefaultServeMux.HandleFunc("/topic/", handleRequest)
w = httptest.NewRecorder()
os.Exit(m.Run())
}
```
# 导航 #
- 上一节:[testing - 其他功能](09.5.md)
- 下一节:[总结](09.7.md)
- 简介
- 第一章 输入输出 (Input/Output)
- 1.1 io — 基本的 IO 接口
- 1.2 ioutil — 方便的 IO 操作函数集
- 1.3 fmt — 格式化 IO
- 1.4 bufio — 缓存 IO
- 第二章 文本
- 2.1 strings — 字符串操作
- 2.2 bytes — byte slice 便利操作
- 2.3 strconv — 字符串和基本数据类型之间转换
- 2.4 regexp — 正则表达式
- 2.5 unicode — Unicode 码点、UTF-8/16 编码
- 第三章 数据结构与算法
- 3.1 sort — 排序算法
- 3.2 index/suffixarray — 后缀数组实现子字符串查询
- 3.3 container — 容器数据类型:heap、list 和 ring
- 第四章 日期与时间
- 4.1 主要类型概述
- 4.2 时区
- 4.3 Time类型详解
- 4.4 定时器
- 第六章 文件系统
- 6.1 os — 平台无关的操作系统功能实现
- 6.2 path/filepath — 操作路径
- 第七章 数据持久存储与交换
- 7.1 database/sql — SQL/SQL-Like 数据库操作接口
- 第八章 数据压缩与归档
- 8.1 flate * DEFLATE 压缩算法
- 第九章 测试
- 9.1 testing * 单元测试
- 9.2 testing * 基准测试
- 9.3 testing * 子测试
- 9.4 testing * 运行并验证示例
- 9.5 testing * 其他功能
- 9.6 httptest * HTTP 测试辅助工具
- 9.7 总结
- 第十章 进程、线程与 goroutine
- 10.1 创建进程
- 10.2 进程属性和控制
- 10.3 线程
- 10.4 进程间通信
- 第十三章 应用构建 与 debug
- 13.1 flag * 命令行参数解析
- 13.2 log * 日志记录
- 13.3 expvar * 公共变量的标准化接口
- 13.4 runtime/debug * 运行时的调试工具