Go 单元测试工具
测试分为4个层次
- 单元测试:对代码进行测试
- 集成测试:对一个服务的接口测试
- 端到端测试(链路测试):从一个链路的入口输入测试用例,验证输出的系统的结果
- UI测试
常犯的错误:
- 没有断言。没有断言的单测是没有灵魂的。
单测的特征:
- A:(Automatic,自动化):单元测试应该是全自动执行的,并且非交互式的
- I:(Independent,独立性):为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
- R:(Repeatable,可重复):单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。
单测
代码 bug 总是在所难免, 越早发现问题解决成本越低, 单测可以尽早的暴露错误。提高代码之路,使得项目更高质量的交付。 起码有三个优点:
- 提高代码质量
编写单测是自测的一部分,编写新代码时增加相应的单测,可以帮助我们发现大部分的bug,有助于减少联调时的调整,提高联调效率。
- 花更少的时间进行功能测试
功能测试成本相对较高,因为经常需要执行一系列操作以验证结果是否符合预期。如果问题如果发现了问题,沟通和复测往往要花费很多的时间。
- 花更少的时间进行回归测试
回归测试是为了避免在对应用程序进行更改时引入bug。测试人员不仅要测试他们的新特性,还要测试以前存在的特性,以验证之前实现的特性是否仍然像预期的那样运行。 通过单元测试,可以在每次构建之后,重新运行整个测试流程,以确保新代码不会破坏已有功能
- 测试异常场景
一些异常的场景QA不好构造,比如并发出款是否资金安全,事务异常相关测试等等。而问题经常出现在这些异常的场景,可能引发线上问题甚至是事故。 而单元测试可通过mock的方式方便的模拟各种异常场景。
Go 单元测试工具
gomonkey
引入 gomonkey 有如下好处:
- 隔离被测代码
- 加速执行测试
- 使执行变得确定
- 模拟特殊情况
功能列表
- 支持为一个函数打一个桩
- 支持为一个函数打一个特定的桩序列
- 支持为一个成员方法打一个桩
- 支持为一个成员方法打一个特定的桩序列
- 支持为一个函数变量打一个桩
- 支持为一个函数变量打一个特定的桩序列
- 支持为一个接口打桩
- 支持为一个接口打一个特定的桩序列
- 支持为一个全局变量打一个桩
函数打桩, 对变量的 mock 实现原理跟 gostub 一样都是通过 reflect 包实现的。除了 mock 变量,gomonkey 还可以直接 mock 导出函数/方法、mock 代码所在包的非导出函数
下载文件,然后再 cp
gomonkey 提供了如下 mock 方法:
- ApplyGlobalVar(target, double interface{}):使用 reflect 包,将 target 的值修改为 double
- ApplyFuncVar(target, double interface{}):检查 target 是否为指针类型,与 double 函数声明是否相同,最后调用 ApplyGlobalVar
- ApplyFunc(target, double interface{}):修改 target 的机器指令,跳转到 double 执行
- ApplyMethod(target reflect.Type, methodName string, double interface{}):修改 method 的机器指令,跳转到 double 执行
- ApplyFuncSeq(target interface{}, outputs []OutputCell):修改 target 的机器指令,跳转到 gomonkey 生成的一个函数执行,每次调用会顺序从 outputs 取出一个值返回
- ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell):修改 target 的机器指令,跳转到 gomonkey 生成的一个方法执行,每次调用会顺序从 outputs 取出一个值返回
- ApplyFuncVarSeq(target interface{}, outputs []OutputCell):gomonkey 生成一个函数顺序返回 outputs 中的值,调用 ApplyGlobalVar
gomonkey 打桩失败的可能原因
- gomonkey 不是并发安全的。如果有多协程并发对同一个目标的打桩的情况,则需要将之前的协程先优雅退出。
- 打桩目标为内联的函数或成员方法。可通过命令行参数 -gcflags=-l (go1.10 版本之前)或-gcflags=all=-l(go1.10 版本及之后)关闭内联优化。
- gomonkey 对于私有成员方法的打桩失败。go1.6 版本的反射机制支持私有成员方法的查询,而 go1.7 及之后的版本却不支持,所以当用户使用 go1.7 及之后的版本时,gomonkey 对于私有成员方法的打桩会触发异常。
goconvey
为全局变量打一个桩
执行结果:
=== RUN TestApplyGlobalVar
..
2 total assertions--- PASS: TestApplyGlobalVar (0.00s)
PASS
为一个函数打桩
结果:
=== RUN TestFunc
expected %v, got %v 2 3
mock_func_test.go:91: expected 2, got 3
--- FAIL: TestFunc (0.00s)FAIL
mock
有时会遇到mock失效的情况,这个问题一般是内联导致的。
什么是内联?
为了减少函数调用时的堆栈等开销,对于简短的函数,会在编译时,直接内嵌调用的代码。
go test -v -gcflags=-l mock_func_test.go
执行结果:
=== RUN TestFunc
expected %v, got %v 2 2
--- PASS: TestFunc (0.00s)
PASS
gcflagsgo tool compile --helpgcflags