mock/stub 测试,当待测试的函数/对象的依赖关系很复杂,并且有些依赖不能直接创建,例如数据库连接、文件I/O等。这种场景就非常适合使用 mock/stub 测试。简单来说,就是用 mock 对象模拟依赖项的行为。

一 、go mock 安装

  1. go get -u github.com/golang/mock/gomock
  2. go get -u github.com/golang/mock/mockgen

image.png

二、 一个简单的demo

  1. 假设 DB 是代码中负责与数据库交互的部分(在这里用 map 模拟),测试用例中不能创建真实的数据库连接。这个时候,如果我们需要测试 GetFromDB 这个函数内部的逻辑,就需要 mock 接口 DB。 ```go // db.go type DB interface { Get(key string) (int, error) }

func GetFromDB(db DB, key string) int { if value, err := db.Get(key); err == nil { return value }

  1. return -1

}

  1. 2. 使用 mockgen 生成 db_mock.go。一般传递三个参数。包含需要被mock的接口得到源文件source,生成的目标文件destination,包名package
  2. ** $ mockgen -source=db.go -destination=db_mock.go -package=main**
  3. 3. 写测试用例,db_test.go
  4. ```go
  5. func TestGetFromDB(t *testing.T) {
  6. ctrl := gomock.NewController(t)
  7. defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用
  8. m := NewMockDB(ctrl)
  9. m.EXPECT().Get(gomock.Eq("Tom")).Return(100, errors.New("not exist"))
  10. if v := GetFromDB(m, "Tom"); v != -1 {
  11. t.Fatal("expected -1, but got", v)
  12. }
  13. }

这个测试用例有2个目的:
一是使用 ctrl.Finish() 断言 DB.Get() 被是否被调用,如果没有被调用,后续的 mock 就失去了意义;
二是测试方法 GetFromDB() 的逻辑是否正确(如果 DB.Get() 返回 error,那么 GetFromDB() 返回 -1)。
NewMockDB() 的定义在 db_mock.go 中,由 mockgen 自动生成。

  1. 执行测试结果

image.png

三、 打桩(stubs)

在上面的例子中,当 Get() 的参数为 Tom,则返回 error,这称之为打桩(stub),有明确的参数和返回值是最简单打桩方式。除此之外,检测调用次数、调用顺序,动态设置返回值等方式也经常使用。

3.1 参数(Eq, Any, Not, Nil)

  1. m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))
  2. m.EXPECT().Get(gomock.Any()).Return(630, nil)
  3. m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil)
  4. m.EXPECT().Get(gomock.Nil()).Return(0, errors.New("nil"))
  • Eq(value) 表示与 value 等价的值。
  • Any() 可以用来表示任意的入参。
  • Not(value) 用来表示非 value 以外的值。
  • Nil() 表示 None 值。

3.2 返回值(Return, DoAndReturn)

  1. m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil)
  2. m.EXPECT().Get(gomock.Any()).Do(func(key string) {
  3. t.Log(key)
  4. })
  5. m.EXPECT().Get(gomock.Any()).DoAndReturn(func(key string) (int, error) {
  6. if key == "Sam" {
  7. return 630, nil
  8. }
  9. return 0, errors.New("not exist")
  10. })
  • Return 返回确定的值
  • Do Mock 方法被调用时,要执行的操作吗,忽略返回值。
  • DoAndReturn 可以动态地控制返回值。

3.3 调用次数(Times)

  1. func TestGetFromDB(t *testing.T) {
  2. ctrl := gomock.NewController(t)
  3. defer ctrl.Finish()
  4. m := NewMockDB(ctrl)
  5. m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil).Times(2)
  6. GetFromDB(m, "ABC")
  7. GetFromDB(m, "DEF")
  8. }
  • Times() 断言 Mock 方法被调用的次数。
  • MaxTimes() 最大次数。
  • MinTimes() 最小次数。
  • AnyTimes() 任意次数(包括 0 次)。

3.4 调用顺序(InOrder)

  1. func TestGetFromDB(t *testing.T) {
  2. ctrl := gomock.NewController(t)
  3. defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用
  4. m := NewMockDB(ctrl)
  5. o1 := m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))
  6. o2 := m.EXPECT().Get(gomock.Eq("Sam")).Return(630, nil)
  7. gomock.InOrder(o1, o2)
  8. GetFromDB(m, "Tom")
  9. GetFromDB(m, "Sam")
  10. }