Golang Mock 四大宗师 gomock、monkey、httptest、sqlmock

简单总结下 Golang Mock 测试中的四大宗师:gomock、monkey、httptest、sqlmock

gomock

特点:强依赖interface进行打桩

  • github.com/golang/mock(不再更新)
  • go.uber.org/mock

GoMock 的使用通常遵循如下四个基本步骤:

  1. 使用 mockgen 为你想要 mock 的接口生成一个 mock
  2. 在你的测试代码中,创建一个 gomock.Controller 实例并把它作为参数传递给 mock 对象的构造函数来创建一个 mock 对象。
  3. 调用 EXPECT() 为你的 mock 对象设置各种期望和返回值。
  4. 调用 mock 控制器的 Finish() 以验证 mock 的期望行为。

参考 [译]GoMock快速上手教程

1
2
3
4
5
6
7
package person

//go:generate mockgen -destination=../mocks/mock_male.go -package=mocks github.com/EDDYCJY/mockd/person Male

type Male interface {
Get(id int64) error
}

注意注释中的 //go:generate 之间不能有空格,这样 go generate 才可以把注释当做一条命令来处理。

关于 go:generate 注释以及注释中包含哪些接口,可以遵循如下原则:

  1. 每个包含需要 mock 接口的文件都添加一条 go:generate 注释
  2. 注释中的 mockgen 命令包括该文件中所有要 mock 的接口
  3. 将所有生成的 mock 文件全部放在 mocks 包中并统一命名,即X.go文件的mock代码输出到 mocks/mock_X.go

通过以上方式 mockgen 命令既能和实际要 mock 的接口放在一起,又能避免针对每个接口单独调用 mockgen和维护输出文件的麻烦。

测试

1
2
3
4
mockgen -source=./person/male.go -destination=../mocks/mock_male.go -package=mocks
go test -cover ./user
go test ./... -coverprofile=cover.out
go tool cover -html=cover.out

比较 counterfeiter

monkey

特点:利用 猴子补丁(monkey patching),替换改写方法,成员方法、全局变量

优化版 monkey

https://github.com/go-kiss/monkeyBouke 的项目做了优化,不同协程可以独立 patch 同一个函数而互不影响,从而可以并发运行单元测试。

工作原理请参考系列文章Go语言实现猴子补丁

注意事项:

  1. 同样需要关闭 Go 语言的内联优化才能生效。
  2. 需要在运行的时候修改内存代码段,因而无法在一些对安全性要求比较高的系统上工作。
  3. 不应该用于生产系统,但用来 mock 测试代码还是没有问题的。
  4. 目前仅支持 amd64 指令架构,支持 linux/macos/windows 平台。

httptest

Go 本身提供了一个非常实用的用于 HTTP 测试的程序包 —— httptest。该包内部 Server 被描述为:

Server 是一个 HTTP 服务器,监听本地环回接口上的系统选择的端口,用于端到端 HTTP 测试。

具体使用可参考 httptest

sqlmock

特点: 通过中间件底层链接进行 mock

  • github.com/DATA-DOG/go-sqlmock
  • github.com/go-redis/redismock
  • github.com/alicebob/miniredis/v2

goconvey

github.com/smartystreets/goconvey,主要特点:

  • 集成于go test
  • 可读的彩色控制台输出
  • 全自动网页用户界面
  • 大量的回归测试
  • 测试代码生成器

安装并启动 gocovey 后,默认在本机 8080 端口提供 WebUI 界面,十分清晰地展现当前项目的单元测试数据。

与其他 Go 测试工具相比的所有功能的综合表:

= Has it

~ = Kind of has it

? = Not sure yet

Feature GoConvey Native go test Testify goblin PrettyTest Ginkgo
Uses go test ~
Web UI
Web UI reports traditional Go tests
Open files in Sublime Text
Auto-test ? ?
Test code generator ~
Custom assertions/matchers ? ~
Optional verbose output ? ?
Colorized console output ?
Non-IT-readable output
Randomized test execution
Coverage report
Skip test blocks/assertions ? ?
Flexible DSL
Supports BDD/TDD/Acceptance
Assertions
- Equal
- DeepEqual
- True
- False
- Nil
- Empty
- (a whole bunch more)

Some table data compiled from Go Test It

有关断言、编写测试、执行等的详细信息,请参阅 文档索引

有关 GoConvey 执行模型的一些重要问题,例如:

  1. 如何定义在每次测试之前运行的 Setup 方法?
  2. 为什么我的嵌套测试不按顺序运行?

令人惊讶的是,这些问题是相关的。例如,考虑伪代码:

1
2
3
4
5
6
Convey A
So 1
Convey B
So 2
Convey C
So 3

我最初认为会执行为 A1B2C3,换句话说,顺序执行。大家都知道,这实际上是先执行 A1B2,然后执行 A1C3。一旦我意识到这一点,我实际上意识到了 goconvey 的强大功能,因为它允许您使用 log(n) 语句进行两次 write n 测试。这种 树状 行为测试消除了很多重复的设置代码,并且更容易阅读完整性(相对于单元测试的页面),同时仍然允许非常好的隔离测试(对于树中的每个分支)。

在上面的伪代码中, Convey A 充当了 Convey BConvey C 的 “Setup” 方法并且分别为每个方法运行。

下面是一个更复杂的示例:

1
2
3
4
5
6
7
8
Convey A
So 1
Convey B
So 2
Convey Q
So 9
Convey C
So 3

你能猜出输出是什么吗?

A1B2Q9A1C3 是正确的答案。

总结

mock 三种主流方案对比:

评估项 gomock monkey sqlmock
代码侵入性 强依赖interface,影响编码规范 无侵入 需要支持动态替换中间件实例
成本 一般
灵活性 每次增加方法都需要修改mock实现 按需mock 工具受限、场景受限

三种不同mock工具适用的场景:

  • gomock:适合于 业务代码分层互相依赖已经使用 interface 情况,或对第三方依赖是 interface 情况下使用
  • monkey:万金油无论是对方法、变量都可以mock,甚至官方函数都行,但不支持并行测试,改写方法是全局生效的
  • sqlmock:不太适合func的单测会增加单测范围以及反调用直觉,比较适合于一一个服务的全流程单测
彦祖老师 wechat