学习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测试的时候如何跳过内联优化。