当待测试的函数/对象的依赖关系很复杂,并且有些依赖不能直接创建,例如数据库连接、文件I/O等。这种场景就非常适合使用 mock/stub 测试。简单来说,就是用 mock 对象模拟依赖项的行为。
gomock
gomock 是官方提供的mock框架,同时还提供了mockgen工具用来辅助申城测试代码:
首先使用如下命令安装:
go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen
然后再使用如下命令生成,
# mockgen -source=[源文件] -destination=[生成后的mock文件] -package=[包名]
会将源文件的接口生成mock对象。
gomonkey 库
试用了官方的,发现要先生成文件等很麻烦,搜索得到可以使用 gomonkey 这个库来mock对象。
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
但是有2点需要注意:
- monkey不支持内联函数,在测试的时候需要通过命令行参数
-gcflags=all=-l
关闭Go语言的内联优化。 - monkey不是线程安全的,所以不要把它用到并发的单元测试中。
gomonkey 有多个版本,我们可以使用最新的v2版本
github.com/agiledragon/gomonkey/v2
mock全局变量
var num = 10
func TestApplyGlobalVar(t *testing.T) {
patches := gomonkey.ApplyGlobalVar(&num, 150)
defer patches.Reset()
assert.Equal(t, num, 150)
}
mock 函数
import (
"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
"testing"
)
func networkCompute(a, b int) (int, error) {
// 假设这里依赖于其他远程服务
c := a + b
return c, nil
}
func Compute(a, b int) (int, error) {
sum, err := networkCompute(a, b)
return sum, err
}
func TestFunc(t *testing.T) {
// mock networkCompute(),返回了计算结果2
patches := gomonkey.ApplyFunc(networkCompute, func(a, b int) (int, error) {
return 2, nil
})
// v2 还可以直接使用:
//patches := gomonkey.ApplyFuncReturn(networkCompute, 2, nil)
defer patches.Reset()
sum, err := Compute(1, 2)
assert.Equal(t, err, nil)
assert.Equal(t, sum, 2)
}
这里有时候会打桩失败,原因是因为函数内联导致的。
mock 方法
type Task struct {
}
func (t *Task) Sub(a, b int) int {
return a - b
}
func (t *Task) add(a, b int) int {
return a + b
}
方法:
func TestTask_Run(t *testing.T) {
task := &Task{}
patches := gomonkey.ApplyMethod(task, "Sub", func(a, b int) int {
return a + b
})
//patches := gomonkey.ApplyMethodReturn(task, "Sub",4)
defer patches.Reset()
result := task.Sub(3, 2)
assert.Equal(t, result, 5)
}
私有方法:
func TestTask_add(t *testing.T) {
task := &Task{}
patches := gomonkey.ApplyPrivateMethod(task, "add", func(_ *Task, a, b int) int {
return a * b
})
defer patches.Reset()
result := task.add(3, 2)
assert.Equal(t, result, 6)
}
网友评论