Go单元测试-- 全能打桩工具Monkey的使用介绍

这是Go语言单元测试系列教程的第5篇,介绍如何在单元测试中使用monkey进行打桩。

在上一篇《Go单元测试--Mock接口实现和对接口打桩》中,我们介绍了如何在单元测试中使用gomockgostub工具mock接口及打桩。

在这一篇中我们将介绍一个更强大的打桩工具——monkey,它支持为任意函数及方法进行打桩。

monkey

介绍

monkey是一个Go单元测试中十分常用的打桩工具,它在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现,其原理类似于热补丁。

monkey库很强大,但是使用时需注意以下事项:

  • monkey不支持内联函数,在测试的时候需要通过命令行参数-gcflags=-l关闭Go语言的内联优化。

  • monkey不是线程安全的,所以不要把它用到并发的单元测试中。

安装

go get bou.ke/monkey

使用示例

假设你们公司中台提供了一个用户中心的库varys,使用这个库可以很方便的根据uid获取用户相关信息。但是当你编写代码的时候这个库还没实现,或者这个库要经过内网请求但你现在没这能力,这个时候要为MyFunc编写单元测试,就需要做一些mock工作。

// 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进行打桩。

// func_test.go

func TestMyFunc(t *testing.T) {
 // 对 varys.GetInfoByUID 进行打桩
 // 无论传入的uid是多少,都返回 &varys.UserInfo{Name: "liwenzhou"}, nil
 monkey.Patch(varys.GetInfoByUID, func(int64)(*varys.UserInfo, error) {
  return &varys.UserInfo{Name: "liwenzhou"}, nil
 })

 ret := MyFunc(123)
 if !strings.Contains(ret, "liwenzhou"){
  t.Fatal()
 }
}

执行单元测试:

注意:这里为防止内联优化添加了-gcflags=-l参数。

go test -run=TestMyFunc -v -gcflags=-l

输出:

=== RUN   TestMyFunc
--- PASS: TestMyFunc (0.00s)
PASS
ok      monkey_demo     0.009s

除了对函数进行mock外monkey也支持对方法进行mock。

// 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进行打桩。

// 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 test -run=User -v
=== RUN   TestUser_GetInfo
--- PASS: TestUser_GetInfo (0.00s)
PASS
ok      monkey_demo     0.012s

monkey基本上能满足我们在单元测试中打桩的任何需求。

社区中还有一个参考monkey库实现的gomonkey库,原理和使用过程基本相似,这里就不再啰嗦了。除此之外社区里还有一些其他打桩工具如GoStub(上一篇介绍过为全局变量打桩)等。

熟练使用各种打桩工具能够让我们更快速地编写合格的单元测试,为我们的软件保驾护航。

总结

本文通过外部函数依赖及内部方法依赖两个示例,介绍了如何使用monkey对依赖的函数和方法进行打桩。

在下一篇中,我们将介绍编写单元测试时常用的工具——goconvey

更多好文推荐

Go单元测试基础

Go数据库 CRUD 的 Mock 测试

Go模拟服务请求和接口返回

Go Mock接口实现和对接口打桩

- END -

扫码关注公众号「网管叨bi叨」

给网管个星标,第一时间吸我的知识 👆

网管为大家整理了一本超实用的《Go 开发参考书》收集了70多条开发实践。去公众号回复【gocookbook】即刻领取!

觉得有用就点个在看  👇👇👇

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值