惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
C
Cisco Blogs
The Hacker News
The Hacker News
T
Threatpost
S
Schneier on Security
K
Kaspersky official blog
Spread Privacy
Spread Privacy
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News and Events Feed by Topic
爱范儿
爱范儿
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
T
Tenable Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
V
V2EX
Webroot Blog
Webroot Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
Scott Helme
Scott Helme
Simon Willison's Weblog
Simon Willison's Weblog
L
LangChain Blog
W
WeLiveSecurity
Cloudbric
Cloudbric

极客兔兔

Go sync.Cond | Go 语言高性能编程 Go 死码消除与调试(debug)模式 | Go 语言高性能编程 Go sync.Once | Go 语言高性能编程 Go 逃逸分析 | Go 语言高性能编程 2020 年终总结 | 极客兔兔 Go struct 内存对齐 | Go 语言高性能编程 Go 空结构体 struct{} 的使用 | Go 语言高性能编程 控制协程(goroutine)的并发数量 | Go 语言高性能编程 | 极客兔兔 如何退出协程 goroutine (其他场景) | Go 语言高性能编程 如何退出协程 goroutine (超时场景) | Go 语言高性能编程 Go 语言陷阱 - 数组和切片 | Go 语言高性能编程 减小 Go 代码编译后的二进制体积 | Go 语言高性能编程 Go Reflect 提高反射性能 | Go 语言高性能编程 读写锁和互斥锁的性能比较 | Go 语言高性能编程 | 极客兔兔 for 和 range 的性能比较 | Go 语言高性能编程 切片(slice)性能及陷阱 | Go 语言高性能编程 | 极客兔兔 字符串拼接性能及原理 | Go 语言高性能编程 | 极客兔兔 pprof 性能分析 | Go 语言高性能编程 benchmark 基准测试 | Go 语言高性能编程 Go 语言高性能编程 | 极客兔兔 Go 接口型函数的使用场景 | 极客兔兔 Python 简明教程 | 快速入门 | 极客兔兔 Go 语言笔试面试题(代码输出) | 极客面试 | 极客兔兔 动手写RPC框架 - GeeRPC第七天 服务发现与注册中心(registry) | 极客兔兔 动手写RPC框架 - GeeRPC第六天 负载均衡(load balance) 动手写RPC框架 - GeeRPC第五天 支持HTTP协议 | 极客兔兔 动手写RPC框架 - GeeRPC第四天 超时处理(timeout) | 极客兔兔 动手写RPC框架 - GeeRPC第三天 服务注册(service register) 动手写RPC框架 - GeeRPC第二天 支持并发与异步的客户端 | 极客兔兔 动手写RPC框架 - GeeRPC第一天 服务端与消息编码 | 极客兔兔 7天用Go从零实现RPC框架GeeRPC | 极客兔兔 Go 语言笔试面试题(并发编程) | 极客面试 | 极客兔兔 Go 语言笔试面试题(基础语法) | 极客面试 | 极客兔兔 Go 语言笔试面试题汇总 | 极客面试 | 极客兔兔 Go Context 并发编程简明教程 | 快速入门 Go Mmap 文件内存映射简明教程 | 快速入门 动手写ORM框架 - GeeORM第七天 数据库迁移(Migrate) | 极客兔兔 动手写ORM框架 - GeeORM第六天 支持事务(Transaction) | 极客兔兔 动手写ORM框架 - GeeORM第五天 实现钩子(Hooks) | 极客兔兔 动手写ORM框架 - GeeORM第四天 链式操作与更新删除 | 极客兔兔 动手写ORM框架 - GeeORM第三天 记录新增和查询 | 极客兔兔 动手写ORM框架 - GeeORM第二天 对象表结构映射 | 极客兔兔 动手写ORM框架 - GeeORM第一天 database/sql 基础 SQLite 常用命令 | 速查表(Cheat Sheet) 7天用Go从零实现ORM框架GeeORM | 极客兔兔 动手写分布式缓存 - GeeCache第七天 使用 Protobuf 通信 动手写分布式缓存 - GeeCache第六天 防止缓存击穿 | 极客兔兔 动手写分布式缓存 - GeeCache第五天 分布式节点 | 极客兔兔 动手写分布式缓存 - GeeCache第四天 一致性哈希(hash) | 极客兔兔 动手写分布式缓存 - GeeCache第三天 HTTP 服务端 动手写分布式缓存 - GeeCache第二天 单机并发缓存 | 极客兔兔 Go Test 单元测试简明教程 | 快速入门 7天用Go从零实现分布式缓存GeeCache | 极客兔兔 Go WebAssembly (Wasm) 简明教程 | 快速入门 Go RPC & TLS 鉴权简明教程 | 快速入门 Go Protobuf 简明教程 | 快速入门 Go语言动手写Web框架 - Gee第七天 错误恢复(Panic Recover) WSL, Git, Mircosoft Terminal 等常用工具配置 Rust 简明教程 | 快速入门 | 极客兔兔 Go语言动手写Web框架 - Gee第六天 模板(HTML Template) 百宝箱 - 值得收藏的工具网站 | 极客兔兔 Go语言动手写Web框架 - Gee第五天 中间件Middleware | 极客兔兔 Go语言动手写Web框架 - Gee第四天 分组控制Group | 极客兔兔 Go语言动手写Web框架 - Gee第三天 前缀树路由Router | 极客兔兔 博客折腾记(七) - Gitalk Plus | 极客兔兔 Go语言动手写Web框架 - Gee第二天 上下文Context | 极客兔兔 Go2 新特性简明教程 | 快速入门 | 极客兔兔 博客折腾记(六) - 不要为了流量忘记了初心 | 极客兔兔 Go语言动手写Web框架 - Gee第一天 http.Handler | 极客兔兔 7天用Go从零实现Web框架Gee教程 | 极客兔兔 Go Gin 简明教程 | 快速入门 Go 语言简明教程 | 快速入门 | 极客兔兔 机器学习笔试面试题 11-20 | 极客面试 | 极客兔兔 机器学习笔试面试题 1-10 | 极客面试 | 极客兔兔 机器学习笔试面试题汇总 | 极客面试 | 极客兔兔 TensorFlow 2 中文文档 - RNN LSTM 文本分类 TensorFlow 2 中文文档 - TFHub 迁移学习 TensorFlow 2 中文文档 - 卷积神经网络分类 CIFAR-10 TensorFlow 2 中文文档 - 保存与加载模型 TensorFlow 2 中文文档 - 过拟合与欠拟合 TensorFlow 2 中文文档 - 回归预测燃油效率 TensorFlow 2 中文文档 - 特征工程结构化数据分类 TensorFlow 2 中文文档 - IMDB 文本分类 TensorFlow 2 中文文档 - MNIST 图像分类 TensorFlow 2 / 2.0 中文文档 TensorFlow 2.0 (九) - 强化学习 70行代码实战 Policy Gradient 博客折腾记(五) - 友链这件事,没那么简单 | 极客兔兔 博客折腾记(四) - 原创资格是争取来的 | 极客兔兔 TensorFlow 2.0 (八) - 强化学习 DQN 玩转 gym Mountain Car TensorFlow 2.0 (七) - 强化学习 Q-Learning 玩转 OpenAI gym 博客折腾记(三) - 主题设计、彩蛋与阅读量翻倍 | 极客兔兔 TensorFlow 2.0 (六) - 监督学习玩转 OpenAI gym game 博客折腾记(二) - 对搜索引擎的理解 | 极客兔兔 博客折腾记(一) - 极致性能的尝试 | 极客兔兔 Pandas 数据处理(三) - Cheat Sheet 中文版 TensorFlow 2.0 (五) - mnist手写数字识别(CNN卷积神经网络) TensorFlow入门(四) - mnist手写数字识别(制作h5py训练集) | 极客兔兔 TensorFlow入门(三) - mnist手写数字识别(可视化训练) | 极客兔兔 Pandas 数据处理(二) - 筛选数据 | 极客兔兔 Pandas 数据处理(一) - DataFrame 与 Series
Go Mock (gomock)简明教程 | 快速入门
2020-02-14 · via 极客兔兔

gomock

1 gomock 简介

上一篇文章 Go Test 单元测试简明教程 介绍了 Go 语言中单元测试的常用方法,包括子测试(subtests)、表格驱动测试(table-driven tests)、帮助函数(helpers)、网络测试和基准测试(Benchmark)等。这篇文章介绍一种新的测试方法,mock/stub 测试,当待测试的函数/对象的依赖关系很复杂,并且有些依赖不能直接创建,例如数据库连接、文件I/O等。这种场景就非常适合使用 mock/stub 测试。简单来说,就是用 mock 对象模拟依赖项的行为。

GoMock is a mocking framework for the Go programming language. It integrates well with Go’s built-in testing package, but can be used in other contexts too.

gomock 是官方提供的 mock 框架,同时还提供了 mockgen 工具用来辅助生成测试代码。

使用如下命令即可安装:

1
2
go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen

2 一个简单的 Demo

1
2
3
4
5
6
7
8
9
10
11
12

type DB interface {
Get(key string) (int, error)
}

func GetFromDB(db DB, key string) int {
if value, err := db.Get(key); err == nil {
return value
}

return -1
}

假设 DB 是代码中负责与数据库交互的部分(在这里用 map 模拟),测试用例中不能创建真实的数据库连接。这个时候,如果我们需要测试 GetFromDB 这个函数内部的逻辑,就需要 mock 接口 DB

第一步:使用 mockgen 生成 db_mock.go。一般传递三个参数。包含需要被mock的接口得到源文件source,生成的目标文件destination,包名package

1
$ mockgen -source=db.go -destination=db_mock.go -package=main

第二步:新建 db_test.go,写测试用例。

1
2
3
4
5
6
7
8
9
10
11
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

m := NewMockDB(ctrl)
m.EXPECT().Get(gomock.Eq("Tom")).Return(100, errors.New("not exist"))

if v := GetFromDB(m, "Tom"); v != -1 {
t.Fatal("expected -1, but got", v)
}
}
  • 这个测试用例有2个目的,一是使用 ctrl.Finish() 断言 DB.Get() 被是否被调用,如果没有被调用,后续的 mock 就失去了意义;
  • 二是测试方法 GetFromDB() 的逻辑是否正确(如果 DB.Get() 返回 error,那么 GetFromDB() 返回 -1)。
  • NewMockDB() 的定义在 db_mock.go 中,由 mockgen 自动生成。

最终的代码结构如下:

1
2
3
4
project/
|--db.go
|--db_mock.go // generated by mockgen
|--db_test.go

执行测试:

1
2
3
4
5
6
$ go test . -cover -v
=== RUN TestGetFromDB
--- PASS: TestGetFromDB (0.00s)
PASS
coverage: 81.2% of statements
ok example 0.008s coverage: 81.2% of statements

3 打桩(stubs)

在上面的例子中,当 Get() 的参数为 Tom,则返回 error,这称之为打桩(stub),有明确的参数和返回值是最简单打桩方式。除此之外,检测调用次数、调用顺序,动态设置返回值等方式也经常使用。

3.1 参数(Eq, Any, Not, Nil)

1
2
3
4
m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))
m.EXPECT().Get(gomock.Any()).Return(630, nil)
m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil)
m.EXPECT().Get(gomock.Nil()).Return(0, errors.New("nil"))
  • Eq(value) 表示与 value 等价的值。
  • Any() 可以用来表示任意的入参。
  • Not(value) 用来表示非 value 以外的值。
  • Nil() 表示 None 值

3.2 返回值(Return, DoAndReturn)

1
2
3
4
5
6
7
8
9
10
m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil)
m.EXPECT().Get(gomock.Any()).Do(func(key string) {
t.Log(key)
})
m.EXPECT().Get(gomock.Any()).DoAndReturn(func(key string) (int, error) {
if key == "Sam" {
return 630, nil
}
return 0, errors.New("not exist")
})
  • Return 返回确定的值
  • Do Mock 方法被调用时,要执行的操作吗,忽略返回值。
  • DoAndReturn 可以动态地控制返回值。

3.3 调用次数(Times)

1
2
3
4
5
6
7
8
9
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

m := NewMockDB(ctrl)
m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil).Times(2)
GetFromDB(m, "ABC")
GetFromDB(m, "DEF")
}
  • Times() 断言 Mock 方法被调用的次数。
  • MaxTimes() 最大次数。
  • MinTimes() 最小次数。
  • AnyTimes() 任意次数(包括 0 次)。

3.4 调用顺序(InOrder)

1
2
3
4
5
6
7
8
9
10
11
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

m := NewMockDB(ctrl)
o1 := m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))
o2 := m.EXPECT().Get(gomock.Eq("Sam")).Return(630, nil)
gomock.InOrder(o1, o2)
GetFromDB(m, "Tom")
GetFromDB(m, "Sam")
}

4 如何编写可 mock 的代码

写可测试的代码与写好测试用例是同等重要的,如何写可 mock 的代码呢?

  • mock 作用的是接口,因此将依赖抽象为接口,而不是直接依赖具体的类。
  • 不直接依赖的实例,而是使用依赖注入降低耦合性。

在软件工程中,依赖注入的意思为,给予调用方它所需要的事物。 “依赖”是指可被方法调用的事物。依赖注入形式下,调用方不再直接指使用“依赖”,取而代之是“注入” 。“注入”是指将“依赖”传递给调用方的过程。在“注入”之后,调用方才会调用该“依赖”。传递依赖给调用方,而不是让让调用方直接获得依赖,这个是该设计的根本需求。
依赖注入 - Wikipedia

如果 GetFromDB() 方法长这个样子

1
2
3
4
5
6
7
8
func GetFromDB(key string) int {
db := NewDB()
if value, err := db.Get(key); err == nil {
return value
}

return -1
}

DB 接口的 mock 并不能作用于 GetFromDB() 内部,这样写是没办法进行测试的。那如果将接口 db DB 通过参数传递到 GetFromDB(),那么就可以轻而易举地传入 Mock 对象了。



上一篇 « 动手写分布式缓存 - GeeCache第三天 HTTP 服务端 下一篇 » 动手写分布式缓存 - GeeCache第四天 一致性哈希(hash)