您的位置 首页 golang

golang单元测试



简介

golang单测,有一些约定,例如文件名是xxx.go,那么对应的测试文件就是xxx_test.go,单测的函数都需要是Test开头,然后使用go test命令,有时发现mock不住,一般都是内联(简短)函数mock失败,可以执行的时候加上编译条件禁止内联 -gcflags=all=-l

1. gomonkey

gomonkey用于mock跑单测,有以下的功能:

  • 为函数打桩
  • 为成员方法打桩
  • 为全局变量打桩
  • 为函数变量打桩
  • 为函数打一个特定的桩序列
  • 为成员方法打一个特定的桩序列
  • 为函数变量打一个特定的桩序列

下面依次说说这几种方法的使用

1.1 使用

1.1.1 mock函数 ApplyFunc

// @param[in] target 被mock的函数// @param[in] double 桩函数定义// @retval patches 测试完成后,通过patches调用Reset删除桩func ApplyFunc(target, double interface{}) *Patchesfunc (this *Patches) ApplyFunc(target, double interface{}) *Patches

桩函数的入参、返回值和要被mock的函数保持一致。

举个例子,例如现有调用链:logicFunc()-> netWorkFunc()
我们要测试logicFunc,而logicFunc里面调用了一个netWorkFunc,因为本地单测一般不会进行网络调用,所以我们要mock住netWorkFunc。

代码实例:

package mainimport (    "fmt"    "testing"    "github.com/agiledragon/gomonkey"    "github.com/smartystreets/goconvey/convey")func logicFunc(a,b int) (int, error) {    sum, err := netWorkFunc(a, b)    if err != nil {        return 0, err    }    return sum, nil}func netWorkFunc(a,b int) (int,error){    if a < 0 && b < 0 {        errmsg := "a<0 && b<0" //gomonkey有bug,函数一定要有栈分配变量,不然mock不住        return 0, fmt.Errorf("%v",errmsg)    }    return a+b, nil}func TestMockFunc(t *testing.T) {    convey.Convey("TestMockFunc1", t, func() {        var p1 = gomonkey.ApplyFunc(netWorkFunc, func(a, b int) (int, error) {            fmt.Println("in mock function")            return a+b, nil        })        defer p1.Reset()        sum, err := logicFunc(10, 20)        convey.So(sum, convey.ShouldEqual, 30)        convey.So(err, convey.ShouldBeNil)    })}

直接用gomonkey.ApplyFunc,来mock netWorkFunc这个函数,然后调用logicFun,再用断言判断一致返回值是否符合预期。

这里用了convey包做断言,这本包断言挺丰富的,用起来很方便,也很简单:

convey.Convey("case的名字", t, func() {  具体测试case  convey.So(...) //断言})

1.1.2 mock成员方法 ApplyMethod

method和function不同,实际上是属于类型的一部分,不像函数属于包的一部分,在函数地址的分配上会有所不同,因此不能直接用ApplyFunc去mock,这时就需要使用ApplyMethod了。

// @param[in] target 被mock的类型// @param[in] methodName 要被mocket的函数名字,是个string// @param[in] double 桩函数定义// @retval patches 测试完成后,通过patches调用Reset删除桩func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patchesfunc (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches

下面的例子和上面ApplyFunc的差不多,也是logicFunc()-> netWorkFunc(),只不过从function变成了method,原理就是利用了reflect中的有几点要注意:

  1. 没办法mock unexported method。原因可以看reflect的原理。还有人论证为啥你永远不改测试unexported method:https://medium.com/@thrawn01/why-you-should-never-test-private-methods-f822358e010

  2. 类型T的method只包含receiver是T的;类型*T的method包含receiver是T和*T的

  3. 写桩函数定义时,要把receiver写进去

例子:

type myType struct {}func (m *myType) logicFunc(a,b int) (int, error) {    sum, err := m.NetWorkFunc(a, b)    if err != nil {        return 0, err    }    return sum, nil}func (m *myType) NetWorkFunc(a,b int) (int,error){    if a < 0 && b < 0 {        errmsg := "a<0 && b<0"        return 0, fmt.Errorf("%v",errmsg)    }    return a+b, nil}func TestMockMethod(t *testing.T) {    Convey("TestMockMethod", t, func() {        var p *myType        fmt.Printf("method num:%d\n", reflect.TypeOf(p).NumMethod())        p1 := gomonkey.ApplyMethod(reflect.TypeOf(p), "NetWorkFunc", func(_ *myType, a,b int) (int,error) {            if a < 0 && b < 0 {                errmsg := "a<0 && b<0"                return 0, fmt.Errorf("%v",errmsg)            }            return a+b, nil        })        defer  p1.Reset()        var m myType        sum, err := m.logicFunc(10, 20)        So(sum, ShouldEqual, 30)        So(err, ShouldBeNil)    })}

1.1.3 mock全局变量 ApplyGlobalVar

// @param[in] target 全局变量的地址// @param[in] double 全局变量的桩func ApplyGlobalVar(target, double interface{}) *Patchesfunc (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches

全局变量的mock很简单,直接看代码吧:

var num = 10func TestApplyGlobalVar(t *testing.T) {    Convey("TestApplyGlobalVar", t, func() {        Convey("change", func() {            patches := ApplyGlobalVar(&num, 150)            defer patches.Reset()            So(num, ShouldEqual, 150)        })        Convey("recover", func() {            So(num, ShouldEqual, 10)        })    })}

1.1.4 mock函数变量 ApplyFuncVar

// @param[in] target 函数变量的地址// @param[in] double 桩函数的定义func ApplyFuncVar(target, double interface{}) *Patchesfunc (this *Patches) ApplyFuncVar(target, double interface{}) *Patches

这个也很简单,直接看代码就明白了:

var funcVar = func(a,b int) (int,error) {    if a < 0 && b < 0 {        errmsg := "a<0 && b<0"        return 0, fmt.Errorf("%v",errmsg)    }    return a+b, nil}func TestMockFuncVar(t *testing.T) {    Convey("TestMockFuncVar", t, func() {        gomonkey.ApplyFuncVar(&funcVar, func(a,b int)(int,error) {            return a-b, nil        })                v, err := funcVar(20, 5)        So(v, ShouldEqual, 15)        So(err, ShouldBeNil)    })}

1.1.5 mock函数序列 ApplyFuncSeq

有一种场景,被mock的函数,可能会被多次调用,我们希望按固定的顺序,然后每次调用的返回值都不一样,我们可以用一个全局变量记录这是第几次调用,然后桩函数里面做判断,更简洁的方法,就是用ApplyFuncSeq

type Params []interface{}type OutputCell struct {    Values Params    Times  int}// @param[in] target 要被mocket的函数// @param[in] outputs 返回值func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patchesfunc (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches

其中Values是返回值,是一个[]interface{},对应实际可能有多个返回值。

看一下例子:

func getInt() (int) {    a := 1    fmt.Println("not in mock")    return a}func TestMockFuncSeq(t *testing.T) {    Convey("func seq", t, func() {        outputs := []gomonkey.OutputCell{            {Values:gomonkey.Params{2}, Times:1},            {Values:gomonkey.Params{1}, Times:0},            {Values:gomonkey.Params{3}, Times:2},        }        var p1 = gomonkey.ApplyFuncSeq(getInt, outputs)        defer p1.Reset()        So(getInt(), ShouldEqual, 2)        So(getInt(), ShouldEqual, 1)        So(getInt(), ShouldEqual, 3)        So(getInt(), ShouldEqual, 3)    })}

注意:

  1. 对于Times,默认都是1次,填1次和0次其实都是1次
  2. 如果总共会调用N次,实际调用超过N次,那么会报错

1.1.6 mock成员方法序列 ApplyMethodSeq

同样的,既然有 ApplyFunSeq,那么就有 ApplyMethodSeq,基本都是一样的,不演示了

1.1.7 mock函数变量序列 ApplyFuncVarSeq

同样的,既然有 ApplyFunSeq,那么就有 ApplyFunVarSeq,基本都是一样的,不演示了


文章来源:智云一二三科技

文章标题:golang单元测试

文章地址:https://www.zhihuclub.com/7277.shtml

关于作者: 智云科技

热门文章

发表评论

您的电子邮箱地址不会被公开。

网站地图