前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >优雅的使用Go进行单元测试

优雅的使用Go进行单元测试

作者头像
公众号guangcity
发布2020-07-20 15:52:02
2.8K0
发布2020-07-20 15:52:02
举报
文章被收录于专栏:光城(guangcity)光城(guangcity)

Go 单元测试

  • 1.单测工具
代码语言:javascript
复制
// go mock相关:
go get github.com/golang/mock/gomock
go get github.com/golang/mock/mockgen
//stub相关:
go get github.com/prashantv/gostub
// monkey
go get github.com/bouk/monkey
// goconvey
go get github.com/smartystreets/goconvey

2.单测

2.1 调自己

在单元测试过程中,遇到了两个问题,第一个:

代码语言:javascript
复制
func Target() {
    A()
}
func A() {
    // call rpc interface

}

现在我们想测试Target函数,但是由于调用的A函数依赖于自己的某个函数,这里就是A调用了rpc接口拉别人接口数据,我们想mockA接口的目标是,想直接拿到A返回的数据即可,直接采用gomock方式,行不通,自己测试了一下,发现要不断的mock 别人接口所依赖的其他接口,非常麻烦,通过注入代码或者后面第三种方式替换函数即可解决。

实践中只需要按照下面方法来,注入一个getHook函数,在test里面才去赋值:

代码语言:javascript
复制
var getHook func([]string) ([]Info, error)

func Target() {
    if getHook != nil {
        info, err = getHook("xxx")
    } else {
        info, err = handler.getInfo("xxx")
    }
}

其中返回的数据时Info:

代码语言:javascript
复制
type Info struct {
 ID string,
}

test文件中,这样写:

代码语言:javascript
复制
func mockGetInfo(x []string) ([]Info, error) {
    res := []Info{
        {
           ID: "xxx"
        },
    }
    return res, nil
}
func Test_xxx(t *testing.T) {
    // 注入屏蔽测试
    getHook = mockGetInfo // inject
    getHook("xxx")
}

这样,就可以把这个接口给mock掉了。

2.2 直接mock远程调用接口

在代码中,还会有调别人的服务,例如:双方约定Pb rpc协议来调用拉取数据,现有下面这个接口:

代码语言:javascript
复制
type Service interface {
    GetSerData(req *SerReq) (rsp *SerRsp, err error)
}

主函数调用如下

代码语言:javascript
复制
// 请求渲染后台
som := NewServiceClientProxy(opts...)
// 发起rpc调用
rsp, _ := som.GetSerData(&req)

这个就比较简单了,直接采用gomock+gostub即可解决,不需要注入代码及主逻辑,非常方便!

首先,使用mockgen生成相应mock_service.go

代码语言:javascript
复制
mockgen -destination=mocks/mock_service.go -package=mocks com.gcx Service

该命令中解释如下:

  • destination表示生成的目标文件
  • package表示上述文件的包名
  • com.gcx表示mock的接口包名
  • Service表示接口名

使用gostub对proxy进行打桩,可以简单理解位用自己的替换代码中想mock的接口。

代码语言:javascript
复制
ctrl := gomock.NewController(t)
mockedService := mocks.NewMockServiceClientProxy(ctrl)
serStubs := gostub.Stub(&NewServiceClientProxy, func(opts ...client.Option) Service {
    return mockedService
})
defer serStubs.Reset()

随后,我们想通过自己的mock自己想要的数据,只需要下面这样描述预期行为即可:

代码语言:javascript
复制
mockedService.EXPECT().GetSerData(gomock.Any(), gomock.Any(), gomock.Any()).Return(&SerRsp{
    // 填充字段
}, nil).AnyTimes()

2.3 monkey

使用monkey测试,算是最简单的一种方式了,不用自己去打桩,然后替换,也不用像方法1一样进行主逻辑的函数注入,mock谁,我们就替换掉这个方法或者函数就行了,而mockey就是这么直接的。

首先看一下安装问题:正常的 方式为:

代码语言:javascript
复制
import "github.com/bouk/monkey"

源码指定了 import 方式,因此实际单测中应该:

代码语言:javascript
复制
import "bou.ke/monkey"

此时,需要进入gopath里面:go/pkg/mod/github.com/bouk,重命名文件夹:mv github.com/bouk bou.ke

如何去使用呢,下面举个例子:

假设要测试getNum:

代码语言:javascript
复制
func getNum() {
   // dosomething
    handler := &ProcessHandler{}
    unionInfo, err = handler.GetSerData("xxx")
   // dosomething
}
// 我们想mock掉该接口,该接口具体实现如下:
func (s *ProcessHandler) GetSerData(c []string) ([], error) {
    // dosomething
    return info, err
}

此时我们直接使用

代码语言:javascript
复制
patch

进行替换即可:

代码语言:javascript
复制
var s *ProcessHandler
monkey.PatchInstanceMethod(reflect.TypeOf(s), "GetSerData", func(o *ProcessHandler, x []string) ([]union.CoverInfo, error) {
    res := []Info{
        {
           ID: "xxx"
        },
    }
    return res, nil
})

mockey是有一些坑的,例如:你要测是的函数或者方法不可导出,就会报下面错误:

这里以GetSerData不可导出为例:panic: unknown method GetSerData反射机制的这种差异导致了Monkey框架的缺陷:在go1.6版本中可以成功打桩的首字母小写的方法,当go版本升级后Monkey框架会显式触发panic,表示

代码语言:javascript
复制
unknown method:

具体patch的原理见后面参考。

3.优雅的单测

vscode生成的单测,如下:

代码语言:javascript
复制
func Test_getNum(t *testing.T) {

    tests := []struct {
        name    string
        args    args
        wantErr bool
    }{
        {
            name: "TestGetNum",
            args: args{
                req: &SerReq{
                },
            },
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if err := getNum(tt.args.req); (err != nil) != tt.wantErr {
                t.Errorf("getNum error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

太挫了,来看一下高富帅的:

代码语言:javascript
复制
convey.Convey("getNum invoke test", t, func() {
    type args struct {  
        req *SerReq
    }
    input := args{
        ctx: ctx,
        req: &SerReq{
        
        },
    }
    err := getNum(req)
    convey.So(err, convey.ShouldBeNil)
})

这里引入convey,具体安装见前面。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Go 单元测试
  • 2.单测
    • 2.1 调自己
      • 2.2 直接mock远程调用接口
        • 2.3 monkey
        • 3.优雅的单测
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档