写过一篇关于Go语言单元测试的文章,简单介绍了 testing 库的使用方法。后来发现 testify/require 和 testify/assert 可以大大简化单元测试的写法,完全可以替代 t.Fatalf
和 t.Errorf
,而且代码实现更为简短、优雅。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
再后来,发现了 mockery
库,它可以为 Go interface 生成一个 mocks struct。通过 mocks struct,在单元测试中我们可以模拟所有 normal cases 和 corner cases,彻底消除细节实现上的bug。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mocks 在测试无状态函数 (对应 FP 中的 pure function) 中意义不大,其应用场景主要在于处理不可控的第三方服务、数据库、磁盘读写等。如果这些服务的调用细节已经被封装到 interface 内部,调用方只看到了 interface 定义的一组方法,那么在测试中 mocks 就能控制第三方服务返回任意期望的结果,进而实现对调用方逻辑的全方位测试。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
关于 interface 的诸多用法,我会单独拎出来一篇文章来讲。本文中,我会通过两个例子展示 testify/require
和 mockery
的用法,分别是:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
- 使用
testify/require
简化 table driven test - 使用
mockery
和testify/mock
为 lazy cache 写单元测试
小提示:由于后方代码较多,对于小屏幕设备,建议横屏阅读代码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
准备工作
go get github.com/stretchr/testify文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
go get github.com/vektra/mockery/.../文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
testify/require
首先,我们通过一个简单的例子看下 require 的用法。我们针对函数 Sqrt
进行测试,其实现为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func Sqrt(xfloat64) float64 {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
if x < 0 {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
panic("cannot be negative")文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
if x == 0 {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
return 0文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
a := x / 2文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
b := (a + 2) / 2文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
erro := a - b文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
for erro >= 0.000000001 || erro <=-0.000000001 {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
a = b文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
b = (b + x/b) / 2文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
erro = a - b文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
return b文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
这里我们使用了一个常规的方法实现 Sqrt
,该实现的最大精确度是到小数点后9位(为了方便演示,这里没有对超出9位的部分进行删除)。我们首先测试 x < 0
导致 panic 的情况,看 require
如何使用,测试代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func TestSqrt_Panic(t *testing.T) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
defer func() {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
r := recover()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.Equal(t, "cannot be negative", r)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
_ = Sqrt(-1)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
在上面的函数中,我们只使用 require.Equal
一行代码就实现了运行结果校验。如果使用 testing
来实现的话,变成了三行,并且需要手写一串描述:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
funcTestSqrt_Panic(t *testing.T) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
defer func() {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
r := recover()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
if r.(string) != "cannot be negative" {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
t.Fatalf("期望panic:\"cannot be negative\", 实际得到\"%s\"\n", r)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
_ = Sqrt(-1)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
使用 require
之后,不仅使测试代码更易于编写,而且能够在测试运行失败时,格式化运行结果,方便定位和修改bug。这里你不妨把 -1
改成一个正数,运行 go test
,查看运行结果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
上面我们能够看到 require
库带来的编码和调试效率的上升。在 table driven test 中,我们会有更深刻的体会。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
Table Driven Test
我们仍然以 Sqrt
为例,来看下如何在 table driven test 中使用 require
。这里我们测试的传入常规参数的情况,代码实现如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func TestSqrt(t*testing.T) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
testcases := []struct {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
desc string文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
input float64文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
expect float64文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
desc: "zero",文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
input: 0,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
expect: 0,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
},文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
desc: "one",文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
input: 1,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
expect: 1,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
},文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
desc: "非常小的有理数",文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
input: 0.00000000000000000000000001,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
expect: 0.0,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
},文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
desc: "普通有理数 2.56",文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
input: 2.56,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
expect: 1.6,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
},文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
desc: "应该返回一个无理数",文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
input: 2,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
expect: 1.414213562,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
},文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
for _, ts := range testcases {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
got := Sqrt(ts.input)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
erro := got - ts.expect文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.True(t, erro < 0.000000001&& erro > -0.000000001, ts.desc)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
在上面这个例子,有三点值得注意:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
匿名struct
允许我们填充任意类型的字段,非常方便于构建测试数据集;- 每个
匿名struct
都包含一个desc string
字段,用于描述该测试要处理的状况。在测试运行失败时,非常有助于定位失败位置; - 使用
require
而不是assert
,因为使用require
时,测试失败以后,所有测试都会停止执行。
关于 require
,除了本文中提到的 require.True
, require.Equal
,还有一个比较实用的方法是 require.EqualValues
,它的应用场景在于处理 Go 的强类型问题,我们不妨看一段代码:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func Test_Require_EqualValues(t *testing.T) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// tests will pass文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.EqualValues(t, 12, 12.0,"比较 int32,float64")文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.EqualValues(t, 12, int64(12),"比较 int32,int64")文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// tests will fail文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.Equal(t, 12, 12.0, "比较 int32,float64")文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.Equal(t, 12, int64(12), "比较 int32,int64")文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
更多 require
的方法参考 require’s godoc。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mockery
mockery 与 Go 指令(directive) 结合使用,我们可以为 interface 快速创建对应的 mock struct。即便没有具体实现,也可以被其他包调用。我们通过 LazyCache 的例子来看它的使用方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
假设有一个第三方服务,我们把它封装在 thirdpartyapi
包里,并加入 go directive,代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
package thirdpartyapi文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
//go:generate mockery -name=Client文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
type Client interface {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
Get(key string) (data interface{}, err error)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
我们在 thirdpartyapi 目录下执行 go generate
,在 mocks 目录下生成对应的 mock struct。目录结构如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
~ $ tree thirdpartyapi/文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
thirdpartyapi/文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
├── client.go文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
└── mocks文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
└── Client.go文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
1 directory, 2 files文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
在执行 go generate
时,指令 //go:generate mockery -name=Client
被触发。它本质上是 mockery -name=Client
的快捷方式,优势是 go generate 可以批量执行多个目录下的多个指令(需要多加一个参数,具体可以参考文档)。 此时,我们只有 interface,并没有具体的实现,但是不妨碍在 LazyCache
中调用它,也不妨碍在测试中调用 thirdpartyapi
的 mocks client。为了方便理解,这里把 LazyCache
的实现也贴出来 (忽略 import):文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
//go:generate mockery -name=LazyCache文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// LazyCache defines the methods for the cache文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
type LazyCache interface {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
Get(key string) (data interface{}, err error)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// NewLazyCache returns a default implementation文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func NewLazyCache(client thirdpartyapi.Client, timeout time.Duration) LazyCache {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
return &lazyCacheImpl{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
cacheStore: make(map[string]cacheValueType),文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
thirdPartyClient: client,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
timeout: timeout,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
type cacheValueType struct {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
data interface{}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
lastUpdated time.Time文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
type lazyCacheImpl struct {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
sync.RWMutex文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
cacheStore map[string]cacheValueType文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
thirdPartyClient thirdpartyapi.Client文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
timeout time.Duration //cache有效时间文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// Get implements LazyCache interface文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func (c*lazyCacheImpl) Get(key string) (interface{}, error) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
c.RLock()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
val := c.cacheStore[key]文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
c.RUnlock()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
timeNow := time.Now()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
if timeNow.After(val.lastUpdated.Add(c.timeout)) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// 从第三方服务获取数据并更新cache文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
latest, err := c.thirdPartyClient.Get(key)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
if err != nil {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
return nil, err文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
val = cacheValueType{latest, timeNow}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
c.Lock()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
c.cacheStore[key] = val文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
c.Unlock()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
return val.data, nil文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
为了简单,我们暂时不考虑 cache miss 或 timeout 与cache被更新的时间间隙,大量请求直接打到 thirdpartyapi
可能导致的后果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
介绍测试之前,我们首先了解一下 “控制变量法”,在自然科学中,它被广泛用于各类实验中。在智库百科,它被定义为 指把多因素的问题变成多个单因素的问题,而只改变其中的某一个因素,从而研究这个因素对事物影响,分别加以研究,最后再综合解决的方法。该方法同样适用于计算机科学,尤其是测试不同场景下程序是否能如期望般运行。我们将这种方法应用于本例中 Get
方法的测试。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
在 Get
方法中,可变因素有 cacheStore
、thirdPartyClient
和 timeout
。在测试中,cacheStore
和 timeout
是完全可控的,thirdPartyClient
的行为需要通过 mocks 自定义期望行为以覆盖默认实现。事实上,mocks 的功能要强大的多,下面我们用代码来看。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
给 LazyCache 写测试
这里,我只拿出 Cache Miss Update Failure 一个case 来分析,覆盖所有 case 的代码查看 github repo。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
func TestGet_CacheMiss_Update_Failure(t *testing.T) {文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
testKey := "test_key"文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
errTest := errors.New("test error")文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mockThirdParty := &mocks.Client{}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mockThirdParty.On("Get",testKey).Return(nil, errTest).Once()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mockCache := &lazyCacheImpl{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
memStore: map[string]cacheValueType{},文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
thirdPartyClient: mockThirdParty,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
timeout: testTimeout,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
// 期望cache miss,并从第三方获取数据失败文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
_, gotErr := mockCache.Get(testKey)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
require.Equal(t, errTest, gotErr)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mock.AssertExpectationsForObjects(t,mockThirdParty)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
这里,我们只讨论 mockThirdParty
,主要有三点:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
mockThirdParty.On("Get", testKey).Return(nil, errTest).Once()
用于定义该对象Get
方法的行为:Get
方法接受testKey
作为参数,当且仅当被调用一次时,会返回errTest
。如果同样的参数,被调用第二次,就会报错;_, gotErr := mockCache.Get(testKey)
触发一次上一步中定义的行为;mock.AssertExpectationsForObjects
函数会对传入对象进行检查,保证预定义的期望行为完全被精确地触发;
在 table driven test 中,我们可以通过 mockThirdParty.On
方法定义 Get
针对不同参数返回不同的结果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
在上面的测试中 .Once()
等价于 .Times(1)
。如果去掉 .Once()
,意味着 mockThirdParty.Get
方法可以被调用任意次。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
更多 mockery 的使用方法参考 github。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html
小结
在本文中,我们结合实例讲解了 testify
和 mockery
两个库在单元测试中的作用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/7650.html