什么是gomock
gomock是Go官方提供的mock框架,提供打桩stub和验证调用的功能,它能对代码中的接口类型进行mock,方便编写单元测试
mockgen工具用于针对接口生成mock对象代码(不能mock单独的函数或方法,必须是接口)
gomock通过mockgen
命令生成包含mock对象的 .go 文件,生成的mock对象具备mock+stub的强大功能
安装和使用mockgen
| go get github.com/golang/mock/gomock go insatll github.com/golang/mock/mockgen
|
1
| mockgen -source xxx.go [options]
|
flags
mock和stub
下面使用日常开发的案例进行演示,如博客项目中对文章数据表进行增删改查
想要测试的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func UpdateArticle(c *gin.Context, db dao.ArticleDao, req UpdateArticleREQ) error { article := model.Article{ BaseModel: model.BaseModel{ ID: req.ID, }, Title: req.Title, Content: req.Content, Desc: req.Desc, } if err := db.Update(article); err != nil { log.Errorf("save article err %v", err) common.ResErrCode(c, common.ErrService) return err } log.Infof("update article %s success", article.Title) common.ResSuccessMsg(c) return nil }
|
首先使用文章数据表的数据库操作接口
因为mock是接口类型的测试框架,所以需要构造测试单元函数的相应接口
1 2 3 4 5 6 7
| type ArticleDao interface { Get(id uint) (model.Article, error) List(opt common.ListOption) ([]model.Article, error) Create(article model.Article) error Update(article model.Article) error Delete(article model.Article) error }
|
使用mockgen工具生成相应的mock代码
1
| mockgen -source article.go -destination mock/mock_article.go
|
生成的代码不需要对它们进行编辑,只需要在单元测试时调用它们
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| func TestArticle_Update(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()
mockDao := mocks.NewMockArticleDao(ctrl) mockDao.EXPECT().Update(gomock.Any()).Return(nil)
type args struct { ctx *gin.Context db dao.ArticleDao req UpdateArticleREQ } tests := []struct { name string args args want error }{ { name: "default", args: args{ ctx: &gin.Context{}, db: mockDao, req: UpdateArticleREQ{ ID: 1, Title: "test", Content: "this is test", }, }, want: nil, }, }
for _, test := range tests { t.Run(test.name, func(t *testing.T) { err := UpdateArticle(test.args.ctx, test.args.db, test.args.req) if !reflect.DeepEqual(err, test.want) { t.Errorf("UpdateArticle() = %v, want %v", err, test.want) } }) } }
|
打桩(stub)
软件测试中的打桩是指用一些代码(桩stub)代替目标代码,通常用来屏蔽或补齐业务逻辑中的关键代码方便进行单元测试。
屏蔽:不想在单元测试用引入数据库连接等重资源
补齐:依赖的上下游函数或方法还未实现
上面的单元测试中引入打桩代码,目的是为了在测试 UpdateArticle( ) 中不想连接数据库并进行操作
1
| mockDao.EXPECT().Update(gomock.Any()).Return(nil)
|
结合最开始文章表相关数据库操作接口,表示在测试 UpdateArticle( ) 时如果遇到 Update(article model.Article) error
的函数,无论传入任何参数 (gomock.Any()
) 都返回nil
(Return(nil)
)
1 2 3
| type ArticleDao interface { Update(article model.Article) error }
|
参数
1 2 3
| mockDao.EXPECT().Update(gomock.Any()).Return(nil) mockDao.EXPECT().Update(gomock.Not(value)).Return(nil) mockDao.EXPECT().Update(gomock.Nil()).Return(nil)
|
返回值
1 2 3 4
| mockDao.EXPECT().Update(gomock.Any()).DoAndReturn(func(key string)(error) { t.Logf("input key is %v\n", key) return nil })
|
Return( )
:返回指定值
Do(func)
:执行操作,忽略返回值
DoAndReturn(func)
:执行并返回指定值
调用次数
使用gomock工具mock的方法都会有期望被调用的次数,默认每个mock方法只允许被调用一次
1
| mockDao.EXPECT().Update(gomock.Any()).Return(nil).Times(1)
|
Times()
指定mock方法被调用的次数
MaxTimes()
最大次数
MinTimes()
最小次数
AnyTimes()
任意次数(包括 0 次)