golang单元测试之mock

时间:2022-09-25 23:15:42

搞单元测试,如果碰到这些情况:
1,一个函数,内部包含了很多并且很深的调用,但是如果单单测这个函数,其实实现的功能很简单。
2,一个函数,包含了其他还未实现的调用。
3,函数内部对数据的要求极为苛刻。

那么这时候就可以考虑使用mock来处理。

mock,简而言之就是可以通过注入我们所期望返回的数据,或者我们所期望传递的参数,来避免上面那些情况,其原理则是通过反射来实现。

这次就来看看golang的mock,gomock
gomock是go官方提供的mock解决方案,主要分为两部分:gomock库和mock代码生成工具mockgen。

使用举例:

package metal

type Imetal interface {
    GetName() string
    SetName(string) string
}

type Metal struct {
    Name string
    Exchange string
}

func (self Metal) GetName() string {
    if self.Name==""{
        return "none"
    }
    return self.Name
}

func (self *Metal) SetName(brand string) string {
    self.Name=brand
    return "done"
}

我现在有一个package,其包含了IMetal接口,这个接口下面有两个方法,现在针对这两个方法来进行mock,ps:gomock只支持interface方法的mock。

在mock之前,需要先通过mockgen来生成mock代码,我的源就是上面的IMeta接口。

简单介绍一下mockgen:
它有两种工作模式---source和reflect

source模式
mockgen -source=foo.go [other options]
根据源文件来生成,源文件是包含了一个或多个interface的文件。

reflect模式
mockgen src/package Conn,Driver
一个文件定义了多个interface而你只想对部分interface进行mock,或者interface存在嵌套,使用reflect模式

mock代码生成好之后,接下来是写测试函数。

package metal

import (
    "mock_metal"
    "github.com/golang/mock/gomock"
    "testing"
    "fmt"
)

func GetMetalName(mi Imetal) string {
    mi.GetName()
    return mi.GetName()
}

func SetMetalName(mi Imetal,name string) string {
     return  mi.SetName(name)
}


func TestMetalName(t *testing.T)  {
    mockCtl := gomock.NewController(t)
    defer mockCtl.Finish()
    mockMetal := mock_metal.NewMockImetal(mockCtl)   //mock_metal就是生成的mock代码,以包的形式存在

    m:=new(Metal)
    mockCtl.RecordCall(m,"GetName").Times(1)
    mockCtl.Call(m,"GetName")

    call:=mockMetal.EXPECT().GetName().Return("apple")
    mockMetal.EXPECT().GetName().Return("peer").After(call)   //注入期望的返回值

    mockedBrand:=GetMetalName(mockMetal)

    mockMetal.EXPECT().SetName(gomock.Eq("al")).Do(func(format string) {   //入参校验
     fmt.Println("recv param :",format)
    }).Return("setdone")

    mockMetal.EXPECT().SetName(gomock.Any()).Do(func(format string) {    //入参不做校验
        fmt.Println("recv param :",format)
    }).Return("setdone")

    mockedSetName:=SetMetalName(mockMetal,"al")
    fmt.Println(mockedSetName)

    if "peer"!=mockedBrand{
        t.Error("Get wrong name:", mockedBrand)
    }

    if "setdone"!=mockedSetName{
        t.Error("Set wrong name:", mockedSetName)
    }
}

然后执行go test即可,会发现这些被mock的函数会按照我们定义的行为来执行。

附带两个gomock的官方资料,基本上看完了就可以上手了。

https://godoc.org/github.com/golang/mock/gomock
https://github.com/golang/mock/blob/master/sample/user_test.go

第一个是gomock库的所有方法说明,第二个是官方的例子,里面有如何进行gomock的方法使用。