这是Go语言单元测试系列教程的第4篇,介绍了如何在单元测试中使用gomock和gostub工具mock接口和打桩。

在上一篇《Go单元测试 — 数据库 CRUD 的 Mock 测试》中,我们介绍了如何使用go-sqlmockminiredis工具进行数据库测试。除了网络和数据库等外部依赖之外,我们在开发中也会经常用到各种各样的接口类型。本文就举例来演示如何在编写单元测试的时候对接口类型进行mock以及如何进行打桩。

gomock

gomock是Go官方提供的测试框架,它在内置的testing包或其他环境中都能够很方便的使用。我们使用它对代码中的那些接口类型进行mock,方便编写单元测试。

安装mockgen

互联网开源库更新迭代比较快,建议直接查看官方文档:https://github.com/golang/mock

首先需要确保你的$GOPATH/bin已经加入到环境变量中。

Go版本号<1.16时:

GO111MODULE=on go get github.com/golang/mock/mockgen@v1.6.0

Go版本>=1.16时:

go install github.com/golang/mock/mockgen@v1.6.0

如果是在你的CI流水线中安装,则需要安装与你的CI环境匹配的合适版本。

运行mockgen

mockgen 有两种操作模式:源码(source)模式和反射(reflect)模式。

源码模式

源码模式根据源文件mock接口。它是通过使用 -source 标志启用。在这个模式下可能有用的其他标志是 -imports 和 -aux_files

例如:

mockgen -source=foo.go [other options]

反射模式

反射模式通过构建使用反射来理解接口的程序来mock接口。它是通过传递两个非标志参数来启用的:一个导入路径和一个逗号分隔的符号列表。可以使用 ”.”引用当前路径的包。

例如:

mockgen database/sql/driver Conn,Driver# Convenient for `go:generate`.
mockgen . Conn,Driver

flags

mockgen 命令用来为给定一个包含要mock的接口的Go源文件,生成mock类源代码。它支持以下标志:

  • -source:包含要mock的接口的文件。

  • -destination:生成的源代码写入的文件。如果不设置此项,代码将打印到标准输出。

  • -package:用于生成的模拟类源代码的包名。如果不设置此项包名默认在原包名前添加mock_前缀。

  • -imports:在生成的源代码中使用的显式导入列表。值为foo=bar/baz形式的逗号分隔的元素列表,其中bar/baz是要导入的包,foo是要在生成的源代码中用于包的标识符。

  • -aux_files:需要参考以解决的附加文件列表,例如在不同文件中定义的嵌入式接口。指定的值应为foo=bar/baz.go形式的以逗号分隔的元素列表,其中bar/baz.go是源文件,foo是-source文件使用的文件的包名。

  • -build_flags:(仅反射模式)一字不差地传递标志给go build

  • -mock_names:生成的模拟的自定义名称列表。这被指定为一个逗号分隔的元素列表,形式为Repository = MockSensorRepository,Endpoint=MockSensorEndpoint,其中Repository是接口名称,mockSensorrepository是所需的mock名称(mock工厂方法和mock记录器将以mock命名)。如果其中一个接口没有指定自定义名称,则将使用默认命名约定。

  • -self_package:生成的代码的完整包导入路径。使用此flag的目的是通过尝试包含自己的包来防止生成代码中的循环导入。如果mock的包被设置为它的一个输入(通常是主输入),并且输出是stdio,那么mockgen就无法检测到最终的输出包,这种情况就会发生。设置此标志将告诉 mockgen 排除哪个导入

  • -copyright_file:用于将版权标头添加到生成的源代码中的版权文件

  • -debug_parser:仅打印解析器结果

  • -exec_only:(反射模式) 如果设置,则执行此反射程序

  • -prog_only:(反射模式)只生成反射程序;将其写入标准输出并退出。

  • -write_package_comment:如果为true,则写入包文档注释 (godoc)。(默认为true)

构建mock

这里就以日常开发中经常用到的数据库操作为例,讲解一下如何使用gomock来mock接口的单元测试。

假设有查询MySQL数据库的业务代码如下,其中DB是一个自定义的接口类型:

// db.go// DB 数据接口
type DB interface {Get(key string)(int, error)Add(key string, value int) error
}// GetFromDB 根据key从DB查询数据的函数
func GetFromDB(db DB, key string) int {if v, err := db.Get(key);err == nil{return v}return -1
}

我们现在要为GetFromDB函数编写单元测试代码,可是我们又不能在单元测试过程中连接真实的数据库,这个时候就需要mock DB这个接口来方便进行单元测试。

使用上面提到的 mockgen 工具来为生成相应的mock代码。通过执行下面的命令,我们就能在当前项目下生成一个mocks文件夹,里面存放了一个db_mock.go文件。

mockgen -source=db.go -destination=mocks/db_mock.go -package=mocks

db_mock.go文件中的内容就是mock相关接口的代码了。

我们通常不需要编辑它,只需要在单元测试中按照规定的方式使用它们就可以了。例如,我们编写TestGetFromDB 函数如下:

// db_test.gofunc TestGetFromDB(t *testing.T) {// 创建gomock控制器,用来记录后续的操作信息ctrl := gomock.NewController(t)// 断言期望的方法都被执行// Go1.14+的单测中不再需要手动调用该方法defer ctrl.Finish()// 调用mockgen生成代码中的NewMockDB方法// 这里mocks是我们生成代码时指定的package名称m := mocks.NewMockDB(ctrl)// 打桩(stub)// 当传入Get函数的参数为liwenzhou.com时返回1和nilm.EXPECT().Get(gomock.Eq("liwenzhou.com")). // 参数Return(1, nil).                  // 返回值Times(1)                         // 调用次数// 调用GetFromDB函数时传入上面的mock对象mif v := GetFromDB(m, "liwenzhou.com"); v != 1 {t.Fatal()}
}

打桩(stub)

软件测试中的打桩是指用一些代码(桩stub)代替目标代码,通常用来屏蔽或补齐业务逻辑中的关键代码方便进行单元测试。

屏蔽:不想在单元测试用引入数据库连接等重资源

补齐:依赖的上下游函数或方法还未实现

上面代码中就用到了打桩,当传入Get函数的参数为liwenzhou.com时就返回1, nil的返回值。

gomock支持针对参数、返回值、调用次数、调用顺序等进行打桩操作。

参数

参数相关的用法有:- gomock.Eq(value):表示一个等价于value值的参数 - gomock.Not(value):表示一个非value值的参数 - gomock.Any():表示任意值的参数 - gomock.Nil():表示空值的参数 - SetArg(n, value):设置第n(从0开始)个参数的值,通常用于指针参数或切片

具体示例如下:

m.EXPECT().Get(gomock.Not("q1mi")).Return(10, nil)
m.EXPECT().Get(gomock.Any()).Return(20, nil)
m.EXPECT().Get(gomock.Nil()).Return(-1, nil)

这里单独说一下SetArg的适用场景,假设你有一个需要mock的接口如下:

type YourInterface {SetValue(arg *int)
}

此时,打桩的时候就可以使用SetArg来修改参数的值。

m.EXPECT().SetValue(gomock.Any()).SetArg(0, 7)  // 将SetValue的第一个参数设置为7

返回值

gomock中跟返回值相关的用法有以下几个:

  • Return():返回指定值

  • Do(func):执行操作,忽略返回值

  • DoAndReturn(func):执行并返回指定值

例如:

m.EXPECT().Get(gomock.Any()).Return(20, nil)
m.EXPECT().Get(gomock.Any()).Do(func(key string) {t.Logf("input key is %v\n", key)
})
m.EXPECT().Get(gomock.Any()).DoAndReturn(func(key string)(int, error) {t.Logf("input key is %v\n", key)return 10, nil
})

调用次数

使用gomock工具mock的方法都会有期望被调用的次数,默认每个mock方法只允许被调用一次。

m.EXPECT().Get(gomock.Eq("liwenzhou.com")). // 参数Return(1, nil).                  // 返回值Times(1)                         // 设置Get方法期望被调用次数为1// 调用GetFromDB函数时传入上面的mock对象m
if v := GetFromDB(m, "liwenzhou.com"); v != 1 {t.Fatal()
}
// 再次调用上方mock的Get方法时不满足调用次数为1的期望
if v := GetFromDB(m, "liwenzhou.com"); v != 1 {t.Fatal()
}

gomock为我们提供了如下方法设置期望被调用的次数。

  • Times() 断言 Mock 方法被调用的次数。

  • MaxTimes() 最大次数。

  • MinTimes() 最小次数。

  • AnyTimes() 任意次数(包括 0 次)。

调用顺序

gomock还支持使用InOrder方法指定mock方法的调用顺序:

// 指定顺序
gomock.InOrder(m.EXPECT().Get("1"),m.EXPECT().Get("2"),m.EXPECT().Get("3"),
)// 按顺序调用
GetFromDB(m, "1")
GetFromDB(m, "2")
GetFromDB(m, "3")

此外知名的Go测试库testify目前也提供类似的mock工具—testify/mockmockery

GoStub

GoStub也是一个单元测试中的打桩工具,它支持为全局变量、函数等打桩。

不过我个人感觉它为函数打桩不太方便,我一般在单元测试中只会使用它来为全局变量打桩。

安装

go get github.com/prashantv/gostub

使用示例

这里使用官方文档中的示例代码演示如何使用gostub为全局变量打桩。

// app.go var (configFile = "config.json"maxNum = 10
)func GetConfig() ([]byte, error) {return ioutil.ReadFile(configFile)
}func ShowNumber()int{// ...return maxNum
}

上面代码中定义了两个全局变量和两个使用全局变量的函数,我们现在为这两个函数编写单元测试。

// app_test.goimport ("github.com/prashantv/gostub""testing"
)func TestGetConfig(t *testing.T) {// 为全局变量configFile打桩,给它赋值一个指定文件stubs := gostub.Stub(&configFile, "./test.toml")defer stubs.Reset()  // 测试结束后重置// 下面是测试的代码data, err := GetConfig()if err != nil {t.Fatal()}// 返回的data的内容就是上面/tmp/test.config文件的内容t.Logf("data:%s\n", data)
}func TestShowNumber(t *testing.T) {stubs := gostub.Stub(&maxNum, 20)defer stubs.Reset()// 下面是一些测试的代码res := ShowNumber()if res != 20 {t.Fatal()}
}

执行单元测试,查看结果:

❯ go test -v
=== RUN   TestGetConfigapp_test.go:18: data:blog="liwenzhou.com"
--- PASS: TestGetConfig (0.00s)
=== RUN   TestShowNumber
--- PASS: TestShowNumber (0.00s)
PASS
ok      golang-unit-test-demo/gostub_demo       0.012s

从上面的示例中我们可以看到,在单元测试中使用gostub可以很方便的对全局变量进行打桩,将其mock成我们预期的值从而进行测试。

总结

在日常工作开发中为代码编写单元测试时如何处理代码中的接口类型是十分常见的问题,本文介绍了如何使用gomockmock相关接口和如何使用gostub工具对全局变量进行打桩。

在下一篇中,我们将更进一步,详细介绍如何在编写单元测试时使用更全能的打桩工具——monkey

系列文章推荐:

  1. Go单元测试从入门到放弃—0.单元测试基础

  2. Go单元测试--模拟服务请求和接口返回

  3. Go单测测试 — 数据库 CRUD 的 Mock 测试

- END -

扫码关注公众号「网管叨bi叨」

给网管个星标,第一时间吸我的知识 

Go 单元测试--Mock接口实现和对接口打桩相关推荐

  1. 单元测试mock框架——jmockit实战

    JMockit是google code上面的一个java单元测试mock项目,她很方便地让你对单元测试中的final类,静态方法,构造方法进行mock,功能强大.项目地址在:http://jmocki ...

  2. JMockit学习之mock接口和抽象类

    大型软件项目,往往会在设计的时候进行模块化划分,模块之间存在依赖关系.为了减少各个模块之间的耦合,通过接口进行依赖,各个模块由不同的开发组进行并行开发.如果A模块需要使用B模块的接口,但是B模块由于开 ...

  3. 史上最轻量​!阿里新型单元测试Mock工具开源了

    简介:为了探索更轻量易用的Mock测试手段,阿里云云效团队尝试给工具减负,在主流Mock工具的基础上让Mock的定义和置换干净利落,最终设计了一款极简风格的测试辅助工具TestableMock,无需初 ...

  4. mock接口开发,excel(读,写,修改)

    mock接口开发 首先需要安装  Flask 模块  :pip install flask 然后引用   from flask import request #想获取到请求参数的话,就得用这个 lan ...

  5. mock模拟接口测试 vue_在 Vue-CLI 中引入 simple-mock实现简易的 API Mock 接口数据模拟...

    在 https://www.jb51.net/article/151520.htm这篇文章中,我们介绍了在 Angular-CLI 中引入 simple-mock 的方法. 本文以 Vue-CLI 为 ...

  6. 文本过滤后返回空值_利用Fiddler来Mock接口返回值

    前篇文章介绍了Mock测试的相关理论知识,今天就通过实战操作来演示一下如何通过Fiddler抓包工具来Mock接口返回值. 准备工作 准备一个服务端接口,可以自己用SpringBoot写一个简单的Re ...

  7. 单元测试 - mock异常

    单元测试 - mock异常 参考文章: (1)单元测试 - mock异常 (2)https://www.cnblogs.com/jylsgup/p/11154111.html (3)https://w ...

  8. koa 接口返回数据_node和koa实现数据mock接口

    本文主要和大家介绍node+koa实现数据mock接口的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧,希望能帮助到大家. 基于node+koa实现的mock数据接口 ...

  9. python mock接口怎么用_python接口自动化测试 - mock模块基本使用介绍

    mock简介 py3已将mock集成到unittest库中 为的就是更好的进行单元测试 简单理解,模拟接口返回参数 通俗易懂,直接修改接口返回参数的值 mock作用 解决依赖问题,达到解耦作用 当我们 ...

最新文章

  1. C语言函数集(十八)
  2. ML 03、机器学习的三要素
  3. vue开发搭建(npm安装 + vue脚手架安装)
  4. Divine Array 思维,模拟,结论
  5. 二面蚂蚁金服(交叉面),已拿offer,Java岗定级阿里P6
  6. 轻量小巧的Knife4j v2.0.8源码
  7. 跟随进度而变色进度条效果ios源码
  8. centos下搭建dhcp服务器
  9. 使用虚拟机搭建ClouderaManager平台,并自动部署一个hadoop集群(CDH)
  10. 苹果8黑屏无法强制开机_iphonexsmax死机黑屏,iphonexsmax无法开机
  11. 【莫队算法】URAL - 2080 - Wallet
  12. 不用在PLC内编程,快速实现西门子与欧姆龙、三菱等品牌的PLC之间实时通讯
  13. h3c 链路聚合测试_H3CSE学习之链路聚合
  14. ENSP静态路由配置
  15. python能做什么软件?Python到底能干嘛,一文看懂
  16. 短篇硬科幻小说《勾股:2.013》
  17. 高级程序员的自我修养:如何才能成长为牛逼的高级程序员?
  18. 如何修改静态 IP 地址和动态 IP 地址
  19. 4G车载信息终端TBOX车联网数据采集智能网联解决方案
  20. arcengine-栅格数据详解

热门文章

  1. 贵州:值得做好“水”文章
  2. python中字典和集合的使用
  3. C++ 1 三字符组
  4. 如何让普通用户可以对DBA_SOURCE视图进行闪回查询?
  5. 每天看了哪些技术点,都记录在该文章下面,时常回过头来看看。
  6. Centos:Yum常用参数
  7. Hive UDAF开发
  8. Spring Boot 实际应用(三)发送邮件实现
  9. 浅谈C++ STL中的优先队列(priority_queue)
  10. 服务器主板点不亮排查