6

Go单测从零到溜系列4—使用monkey打桩

 2 years ago
source link: https://www.liwenzhou.com/posts/Go/golang-unit-test-4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Go单测从零到溜系列4—使用monkey打桩

2021年9月21日
| Golang
本文总阅读量6次

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

在上一篇《Go单测从零到溜系列3—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


扫码关注微信公众号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK