本文将介绍如何在单元测试中使用monkey
进行打桩。
monkey支持为任意函数及方法进行打桩。
monkey介绍
monkey
是一个Go
单元测试中十分常用的打桩工具,它在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现,其原理类似于热补丁。
monkey库很强大,但是使用时需注意以下事项:
第一点:monkey
不是线程安全的,所以不要把它用到并发的单元测试中。
第二点: monkey
不支持内联函数,在测试的时候需要通过命令行参数-gcflags=-l
关闭Go
语言的内联优化。执行单测时需要关闭内联优化,这样可以保证mock
成功!!!
如何禁用内联和编译优化
命令行跑单测可以采用:
go
go test -gcflags="all=-l -N" -v ./...
goland
图形界面可以采用:
Debug 模式下跑单个测试时会自动带上该参数,Run 模式下跑单个测试或者跑一个包的测试则需要手动带上该参数
安装
go
go get bou.ke/monkey
使用示例
假设你们公司中台提供了一个用户中心的库varys
,使用这个库可以很方便的根据uid
获取用户相关信息。但是当你编写代码的时候这个库还没实现,或者这个库要经过内网请求但你现在没这能力,这个时候要为MyFunc
(MyFunc中依赖了varys库中相关方法)编写单元测试,就需要做一些mock
工作。
go
// func.go
func MyFunc(uid int64)string{
u, err := varys.GetInfoByUID(uid)
if err != nil {
return "welcome"
}
// 这里是一些逻辑代码...
return fmt.Sprintf("hello %s\n", u.Name)
}
我们使用monkey
库对varys.GetInfoByUID
进行打桩。
go
// func_test.go
func TestMyFunc(t *testing.T) {
// 对 varys.GetInfoByUID 进行打桩,跳转到我们指定的函数
// 无论传入的uid是多少,都返回 &varys.UserInfo{Name: "lym"}, nil
monkey.Patch(varys.GetInfoByUID, func(int64)(*varys.UserInfo, error) {
return &varys.UserInfo{Name: "lym"}, nil
})
ret := MyFunc(123)
if !strings.Contains(ret, "lym"){
t.Fatal()
}
}
执行单元测试:
注意:这里为防止内联优化添加了-gcflags=-l
参数。
go
go test -run=TestMyFunc -v -gcflags=-l
输出:
go
=== RUN TestMyFunc
--- PASS: TestMyFunc (0.00s)
PASS
ok monkey_demo 0.009s
除了对函数进行mock
外,monkey
也支持对方法进行mock
。
go
// method.go
type User struct {
Name string
Birthday string
}
// CalcAge 计算用户年龄
func (u *User) CalcAge() int {
t, err := time.Parse("2006-01-02", u.Birthday)
if err != nil {
return -1
}
return int(time.Now().Sub(t).Hours()/24.0)/365
}
// GetInfo 获取用户相关信息
func (u *User) GetInfo()string{
age := u.CalcAge()
if age <= 0 {
return fmt.Sprintf("%s很神秘,我们还不了解ta。", u.Name)
}
return fmt.Sprintf("%s今年%d岁了,ta是我们的朋友。", u.Name, age)
}
如果我们为GetInfo
编写单元测试的时候CalcAge
方法的功能还未完成,这个时候我们可以使用monkey
进行打桩。
go
// method_test.go
func TestUser_GetInfo(t *testing.T) {
var u = &User{
Name: "q1mi",
Birthday: "1990-12-20",
}
// 为对象方法打桩
monkey.PatchInstanceMethod(reflect.TypeOf(u), "CalcAge", func(*User)int {
return 18
})
ret := u.GetInfo() // 内部调用u.CalcAge方法时会返回18
if !strings.Contains(ret, "朋友"){
t.Fatal()
}
}
执行单元测试:
go
❯ go test -run=User -v
=== RUN TestUser_GetInfo
--- PASS: TestUser_GetInfo (0.00s)
PASS
ok monkey_demo 0.012s
monkey基本上能满足我们在单元测试中打桩的任何需求。
社区中还有一个参考monkey
库实现的gomonkey
库,原理和使用过程基本相似,这里就不再啰嗦了。
熟练使用各种打桩工具能够让我们更快速地编写合格的单元测试,为我们的软件保驾护航。
本文通过外部函数依赖及内部方法依赖两个示例,介绍了如何使用monkey对依赖的函数和方法进行打桩。
在下一篇中,我们将介绍编写单元测试时常用的工具------goconvey。