101. Go单测系列1---使用monkey打桩

本文将介绍如何在单元测试中使用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。

相关推荐
microrain14 小时前
读数是 1008,用电量是 8:SagooIoT 计算服务的增量口径与七种计算策略
物联网·golang·计算服务·sagooiot
看浪的路人16 小时前
第7讲:大模型后端服务化——Go 实现高并发 LLM Proxy / Router / Fallback
开发语言·后端·golang
灯澜忆梦18 小时前
【Go Zap 日志库】#1 |快速上手 + 配置详解
开发语言·后端·golang
不是万能的豆学长21 小时前
排查Go服务40天后的内存泄漏问题
开发语言·后端·golang·go
tachibana221 小时前
Go 语言 Map 的底层实现原理
开发语言·后端·golang·make
tachibana21 天前
Go语言里slice是怎么扩容的?
后端·golang·go·切片
西瓜很甜哟1 天前
golang实现大文件分片上传
后端·golang·html
tachibana22 天前
使用for range 的时候,它的地址会发生变化吗?
开发语言·后端·golang·go·语法
王中阳Go2 天前
Go 后端转 AI:非科班怎么补?一张 3 列路线表(可保存)
开发语言·人工智能·后端·python·golang·大语言模型
for_ever_love__2 天前
LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次
人工智能·golang·langchain·rag·langgraph·agentic·自适应检索