Go语言单元测试

2023-03-24 08:33:26 浏览数 (1)

学习Golang的时候遇到了一些单元测试问题,发现有些工具是真的好用,就记录在此,主要包括monkey、convey,还有数据库Mock等。

1. monkey打桩测试

最近遇到一种编码场景,我需要对一个库函数进行测试,但是这个函数的调用可能会影响现网数据,因此需要做一点欺骗。

monkey就是一种常见的单元测试打桩测试工具,给我的感觉有点像Java的动态代理AOP,直接将函数在运行时注入,实现动态的函数,使得目标函数或者方法逻辑跳转到桩实现上。

下面的代码是这样一种场景,我在计算KPI的时候,相关的函数CalcKPI还没有写好,所以我只能用monkey打桩:

代码语言:go复制
package monkey

import "fmt"

func CalcKPI(name string) {
	goodAtGolang := IsGoodAtGolang(name)
	if goodAtGolang {
		fmt.Printf("KPI Level A!")
	} else {
		fmt.Println("KPI Level C")
	}
}

下面是单元测试代码:

代码语言:go复制
package monkey

import (
	"testing"

	"bou.ke/monkey"
)

func TestCalcKPI(t *testing.T) {
	monkey.Patch(IsGoodAtGolang, func(string) bool {
		return true
	})

	CalcKPI("lyon")
}

使用起来很简单,相当于用闭包写了个伪代码,将逻辑设置成永真,那么测试结果简单可见:

只是注意要在执行命令的时候加上“-gcflags=-l”的选项,以免出现内联优化:go test -run=TestMyFunc -v -gcflags=-l

如果不加上面的选项,那么很有可能monkey桩是不生效的,这是因为编译器对比较简单的函数,在编译的时候,会将其直接内嵌进去。

当然大部分情况下我们还是用方法多于函数,所以monkey也是支持方法的打桩,对上面的例子稍加改造,首先是没有实现的方法:

代码语言:go复制
package monkey

type Employee struct {
	Name        string
	CommitLines int
}

func newEmployee(name string, commitLines int) *Employee {
	return &Employee{Name: name, CommitLines: commitLines}
}

func (e *Employee) IsGoodAtGolang() bool {
	// 未实现的逻辑
	return false
}

接下来是HRBP逻辑:

代码语言:go复制
package monkey

import "fmt"

func CalcKPI(name string, commitLines int) {

	r := newEmployee(name, commitLines)
	b := r.IsGoodAtGolang()
	if b {
		fmt.Printf("KPI Level A!")
	} else {
		fmt.Println("KPI Level C")
	}
}

由于没有写完的逻辑会导致这段代码的输出永远为C,因此测试代码打桩:

代码语言:go复制
package monkey

import (
	"reflect"
	"testing"

	"bou.ke/monkey"
)

func TestCalcKPI(t *testing.T) {
	u := &Employee{Name: "lyon", CommitLines: 100}
	
	monkey.PatchInstanceMethod(reflect.TypeOf(u), "IsGoodAtGolang", func(*Employee) bool {
		return true
	})

	CalcKPI(u.Name, u.CommitLines)
}

上面只是一种很简单的应用。但是monkey的github上举的例子也很简单,基本上是满足日常使用了。

2 Convey单元测试工具

在用Java的时候,单元测试里经常会用到断言Assert工具,JUnit工具就提供了这种支持,Spring框架也支持。

但是在最开始使用Go测试框架的时候发现没有断言这种东西,感觉不可思议,没有断言怎么能成为单元测试。

goconvey工具就提供了很好用的单元测试断言支持。

代码语言:shell复制
go get github.com/smartystreets/goconvey/convey@v1.7.2

还是利用上面的例子,将convey和monkey结合起来使用,改造一下上面的例子,将KPI的返回从void变成string:

代码语言:go复制
package monkey

func CalcKPI(name string, commitLines int) string {

	r := newEmployee(name, commitLines)
	b := r.IsGoodAtGolang()
	if b {
		return "A"
	} else {
		return "C"
	}
}

单元测试代码用convey做一下改造:

代码语言:go复制
package monkey

import (
	"reflect"
	"testing"

	"bou.ke/monkey"
	. "github.com/smartystreets/goconvey/convey"
)

func TestCalcKPI(t *testing.T) {
	
	Convey("简单的测试", t, func() {
		u := &Employee{Name: "lyon", CommitLines: 100}
	
		monkey.PatchInstanceMethod(reflect.TypeOf(u), "IsGoodAtGolang", func(*Employee) bool {
			return true
		})
	
		k := CalcKPI(u.Name, u.CommitLines)
		So(k, ShouldEqual, "A")
	})
}

这里就利用convey编写了一个测试单元,这个单元里可以写各种各样的逻辑,形成一个闭环,并用断言工具So进行判断。

结果输出个人感觉很好看,还有颜色渲染。

至于这个工具的高级用法,那可能就是嵌套了,convey包裹的对象是可以嵌套的。所有的断言方法都可以在官方github查询到。

还有WebUI,提供了很多强大的可视化功能,但是我暂时没发现用WebUI测试的时候如何跳过内联优化。

go

0 人点赞