Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
kevinyan815 opened this issue Apr 25, 2022 · 0 comments
Open

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

kevinyan815 opened this issue Apr 25, 2022 · 0 comments

Comments

@kevinyan815
Copy link
Owner

转载自:https://www.liwenzhou.com/posts/Go/golang-unit-test-3/
示例代码原作者已上传至Github,点击👉🏻https://github.com/Q1mi/golang-unit-test-demo 查看完整源代码。

这是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接口实现和对接口打桩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant