Open wtysos11 opened 3 years ago
来源于KM社区的两篇相关文章,因为公司规定就没有办法放上来了。 https://yizhi.ren/2019/06/15/gocoverage/ https://medium.com/rungo/unit-testing-made-easy-in-go-25077669318
来源于KM社区的两篇相关文章,因为公司规定就没有办法放上来了。
go自带的单元测试工具可以计算代码覆盖率,以下面这个很简单的文件为例 main.go
package main import ( "encoding/json" "fmt" ) type Test2 struct{ C int } type Test1 struct{ A int B Test2 } func Add(a,b int) int{ return a+b } func Reduce(a,b int) int{ if a==0{ return a } return a-b } func main(){ //s := Test1{ // A:5, // B: Test2{ // C:6, // }, //} //st,_ := json.Marshal(s) //fmt.Println(string(st)) st := []byte("{\"A\":5}") real := &Test1{} err := json.Unmarshal(st,real) if err != nil{ fmt.Println(err) }else{ t := Test2{} fmt.Println(real) if real.B == t{ fmt.Println("equal") } } }
test.go
package main import "testing" func TestAdd(t *testing.T) { type args struct { a int b int } tests := []struct { name string args args want int }{ {"test1",args{1,2},3}, {"test1",args{2,2},4}, {"test1",args{3,2},5}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Add(tt.args.a, tt.args.b); got != tt.want { t.Errorf("Add() = %v, want %v", got, tt.want) } }) } } func TestReduce(t *testing.T) { type args struct { a int b int } tests := []struct { name string args args want int }{ {"test1",args{1,2},-1}, {"test1",args{2,2},0}, {"test1",args{3,2},1}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Reduce(tt.args.a, tt.args.b); got != tt.want { t.Errorf("Reduce() = %v, want %v", got, tt.want) } }) } }
go test --covermode=count -coverprofile=cover.out -v -gcflags=all=-l
go tool cover -html=cover.out
当然,覆盖率并不是越高越好,因为很多代码的单元测试性价比并不高。如果以x轴代表投入成本(即开发者的时间和精力),y轴为单元测试带来的收益,那么左上角应该重点关注,右下角应该直接放弃。
普通的单元测试在遇到依赖数据库链接或者Rpc接口的时候就会出现问题。这时候需要使用mock/stub测试: mock的作用:模拟链接的结果 需要用到的库:
mock/stub
一个例子说明patch的作用
func ReadFromRedis(key string) (string, error){ return "kakak", nil } // 从redis里面取值,内部可能还包含其他业务逻辑 func GetValFromRedis(key string) string { val, err := ReadFromRedis(key) if err != nil { return "xx" } fmt.Println("val=", val) return val } //test.go import ( "fmt" "github.com/agiledragon/gomonkey" "github.com/smartystreets/goconvey/convey" "testing" ) func TestGetValFromRedis(t *testing.T) { convey.Convey("one for success", t, func() { patches := gomonkey.ApplyFunc(ReadFromRedis, func(key string)(string, error) { return "123", nil }) val, _ := ReadFromRedis("gggg") fmt.Println("valxxx=", val) defer patches.Reset() got := GetValFromRedis("1212121") convey.So(got, convey.ShouldEqual, "123") }) }
在上述例子中,需要测试的函数为GetValFromRedis,但是因为ReadFromRedis函数是需要访问redis的(领会意思,这里就没有实际访问了),所以正常的测试可能会出现连接问题(比如测试网络肯定是连不上的) 所以,使用gomonkey.ApplyFunc,就可以直接将ReadFromRedis在该函数的作用域下替换掉,这样GetValFromRedis所调用的函数将会是我们新定义的函数。 不过打桩可能会因为函数内联而失败。内联就是说函数会直接在调用处展开。闭包调用、select、for、defer、go创建的协程不会内联,其他的都有可能内联,比如这个。对此,需要在运行单元测试的时候加上-gcflags=all=-l。
GetValFromRedis
ReadFromRedis
gomonkey.ApplyFunc
-gcflags=all=-l
总结:对于一些无法完成本地调用的函数,我们只关心其返回结果,因此可以打桩。
代码覆盖率
go自带的单元测试工具可以计算代码覆盖率,以下面这个很简单的文件为例 main.go
test.go
go test --covermode=count -coverprofile=cover.out -v -gcflags=all=-l
就可以对代码覆盖度进行分析(23.3%)go tool cover -html=cover.out
就可以在HTML中打开分析的代码覆盖度。标绿的行为覆盖过的结果;红色的为未覆盖到的代码。单元测试
Question:哪些代码需要单元测试
当然,覆盖率并不是越高越好,因为很多代码的单元测试性价比并不高。如果以x轴代表投入成本(即开发者的时间和精力),y轴为单元测试带来的收益,那么左上角应该重点关注,右下角应该直接放弃。
mock
普通的单元测试在遇到依赖数据库链接或者Rpc接口的时候就会出现问题。这时候需要使用
mock/stub
测试: mock的作用:模拟链接的结果 需要用到的库:一个例子说明patch的作用
在上述例子中,需要测试的函数为
GetValFromRedis
,但是因为ReadFromRedis
函数是需要访问redis的(领会意思,这里就没有实际访问了),所以正常的测试可能会出现连接问题(比如测试网络肯定是连不上的) 所以,使用gomonkey.ApplyFunc
,就可以直接将ReadFromRedis
在该函数的作用域下替换掉,这样GetValFromRedis
所调用的函数将会是我们新定义的函数。 不过打桩可能会因为函数内联而失败。内联就是说函数会直接在调用处展开。闭包调用、select、for、defer、go创建的协程不会内联,其他的都有可能内联,比如这个。对此,需要在运行单元测试的时候加上-gcflags=all=-l
。总结:对于一些无法完成本地调用的函数,我们只关心其返回结果,因此可以打桩。