扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
  • 宝典简介

    • 《Go语言标准库》The Golang Standard Library by Example
  • 第一章 输入输出 (Input/Output)

  • 第二章 文本

  • 第三章 数据结构与算法

  • 第四章 日期与时间

  • 第五章 数学计算

  • 第六章 文件系统

  • 第七章 数据持久存储与交换

  • 第八章 数据压缩与归档

  • 第九章 测试

    • 第九章 测试
    • testing - 单元测试
    • testing - 基准测试
    • testing - 子测试与子基准测试
    • testing - 运行并验证示例
    • testing - 其他功能
    • httptest - HTTP 测试辅助工具
    • 总结
  • 第十章 进程、线程与 goroutine

  • 第十三章 应用构建 与 debug

  • 第十五章 底层库介绍

  • 第十六章 同步

扫码订阅《 》或入驻星球,即可阅读文章!

httptest - HTTP 测试辅助工具


polaris1119

# httptest - HTTP 测试辅助工具

由于 Go 标准库的强大支持,Go 可以很容易的进行 Web 开发。为此,Go 标准库专门提供了 net/http/httptest 包专门用于进行 http Web 开发测试。

本节我们通过一个社区帖子的增删改查的例子来学习该包。

# 简单的 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"`
}
1
2
3
4
5
6
7
8
9

对于 Topic 的增删改查代码很简单,可以查看完整代码。

接下来,是通过 net/http 包来实现一个 Web 应用。

func main() {
	http.HandleFunc("/topic/", handleRequest)
	http.ListenAndServe(":2017", nil)
}
...
1
2
3
4
5

/topic/ 开头的请求都交由 handleRequest 处理,它根据不同的 Method 执行相应的增删改查,详细代码可以查看 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 应用的接口进行了测试。现在,我们通过 net/http/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)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

首先跟待测试代码一样,配置上路由,对 /topic/ 的请求都交由 handleRequest 处理。

mux := http.NewServeMux()
mux.HandleFunc("/topic/", handleRequest)
1
2

因为 handlePost 的函数签名是 func handlePost(w http.ResponseWriter, r *http.Request) error,为了测试它,我们必须创建 http.ResponseWriter 和 http.Request 的实例。

接下来的代码就是创建一个 http.Request 实例 和一个 http.ResponseWriter 的实例。这里的关键是,通过 httptest.NewRecorder() 可以获得 httptest.ResponseRecorder 结构,而此结构实现了http.ResponseWriter 接口。

reader := strings.NewReader(`{"title":"The Go Standard Library","content":"It contains many packages."}`)
r, _ := http.NewRequest(http.MethodPost, "/topic/", reader)

w := httptest.NewRecorder()
1
2
3
4

准备好之后,可以测试目标函数了。这里,我们没有直接调用 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")
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注意:因为数据没有落地存储,为了保证后面的测试正常,请将 TestHandlePost 放在最前面。

# 测试代码改进

细心的朋友应该会发现,上面的测试代码有重复,比如:

mux := http.NewServeMux()
mux.HandleFunc("/topic/", handleRequest)
1
2

以及:

w := httptest.NewRecorder()
1

这正好是前面学习的 setup 可以做的事情,因此可以使用 TestMain 来做重构。

var w *httptest.ResponseRecorder

func TestMain(m *testing.M) {
	http.DefaultServeMux.HandleFunc("/topic/", handleRequest)

	w = httptest.NewRecorder()

	os.Exit(m.Run())
}
1
2
3
4
5
6
7
8
9
  • 简单的 Web 应用
  • 通过 httptest 进行测试
  • 测试代码改进