使用monkey打桩
Go单测—使用monkey打桩
介绍如何在单元测试中使用monkey进行打桩。将介绍一个更强大的打桩工具——monkey
,它支持为任意函数及方法进行打桩。源码地址
一、monkey介绍
monkey是一个Go单元测试中十分常用的打桩工具,它在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现,其原理类似于热补丁。
monkey库很强大,但是使用时需注意以下事项:
- monkey不支持内联函数,在测试的时候需要通过命令行参数
-gcflags=-l
关闭Go语言的内联优化。 - monkey不是线程安全的,所以不要把它用到并发的单元测试中。
二、安装
go get bou.ke/monkey
三、使用示例
假设你们公司中台提供了一个用户中心的库varys
,使用这个库可以很方便的根据uid获取用户相关信息。但是当你编写代码的时候这个库还没实现,或者这个库要经过内网请求但你现在没这能力,这个时候要为MyFunc
编写单元测试,就需要做一些mock工作。
// func.go
package monkey_demo
import (
"fmt"
"golang-unit-test-example/varys"
)
/*
@author RandySun
@create 2022-05-01-17:31
*/
// MyFunc
// @Description: 获取用户名
// @param uid
// @return string
//
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
进行打桩。
// func_test.go
package monkey_demo
import (
"golang-unit-test-example/varys"
"strings"
"testing"
"bou.ke/monkey"
)
/*
@author RandySun
@create 2022-05-01-17:37
*/
//
// TestMyFunc
// @Description: 为函数打桩
// @param t
//
func TestMyFunc(t *testing.T) {
// 对 varys.GetInfoByUID 进行打桩
// 无论传入uid是多少,都返回 &varys.UserInfo{Name: "RandySun"}, nil
monkey.Patch(varys.GetInfoByUID, func(int64) (*varys.UserInfo, error) {
return &varys.UserInfo{Name: "RandySun"}, nil
})
res := MyFunc(123)
if !strings.Contains(res, "RandySun") {
t.Fatal()
}
t.Logf("name: %s", res)
}
执行单元测试:
注意:这里为防止内联优化添加了
-gcflags=-l
参数。
go test -run TestMy -v -gcflags=-l
输出:
monkey_demo> go test -run TestMy -v -gcflags=-l
=== RUN TestMyFunc
func_test.go:29: name: hello RandySun
--- PASS: TestMyFunc (0.00s)
PASS
ok golang-unit-test-example/08monkey_demo 0.225s
除了对函数进行mock外monkey
也支持对方法进行mock。
// method.go
package monkey_demo
import (
"fmt"
"time"
)
/*
@author RandySun
@create 2022-05-01-17:47
*/
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
}
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进行打桩。
// method_test.go
package monkey_demo
import (
"reflect"
"strings"
"testing"
"bou.ke/monkey"
)
//
// TestUser_GetInfo
// @Description: 为方法打桩
// @param t
//
func TestUser_GetInfo(t *testing.T) {
var u = &User{
Name: "RandySun",
Birthday: "2004-06-30",
}
// 为对象方法打桩
monkey.PatchInstanceMethod(reflect.TypeOf(u), "CalcAge", func(*User) int {
return 18
})
res := u.GetInfo() // 内部调用u.CalcAge方法会返回18
if !strings.Contains(https://img2022.cnblogs.com/blog/1739642/202205/1739642-20220503150438568-1721597240.png 'res, "朋友"') {
t.Fatal()
}
t.Logf("%s", res)
}
执行单元测试:
monkey_demo> go test -run User_ -v
=== RUN TestUser_GetInfo
method_test.go:31: RandySun今年18岁,ta是我们的朋友。
--- PASS: TestUser_GetInfo (0.00s)
PASS
ok golang-unit-test-example/08monkey_demo 0.266s
monkey
基本上能满足我们在单元测试中打桩的任何需求。
社区中还有一个参考monkey库实现的gomonkey库,原理和使用过程基本相似,这里就不再啰嗦了。除此之外社区里还有一些其他打桩工具如GoStub(上一篇介绍过为全局变量打桩)等。
熟练使用各种打桩工具能够让我们更快速地编写合格的单元测试,为我们的软件保驾护航。
四、总结
本文通过外部函数依赖及内部方法依赖两个示例,介绍了如何使用monkey
对依赖的函数和方法进行打桩。
在下一篇中,我们将介绍编写单元测试时常用的工具——goconvey
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人