内容简介:通过阅读上一篇文章,相信你对怎么做单元测试已经有了初步的概念,可以着手对现有的项目进行改造并开展测试了。学会了走路,我们尝试跑起来,本篇主要介绍gomock测试框架,让我们的单元测试更加有效率。当针对某方法进行单元测试的时候,通常不止写一个测试用例,我们需要测试该方法在多种入参条件下是否都能正常工作,特别是要针对边界值进行测试。通常这个时候表格驱动测试就派上用场了——表格驱动测试方法让我们的测试方法更加清晰和简练,减少了复制粘贴,并大大提高的测试代码的可读性。
通过阅读上一篇文章,相信你对怎么做单元测试已经有了初步的概念,可以着手对现有的项目进行改造并开展测试了。学会了走路,我们尝试跑起来,本篇主要介绍gomock测试框架,让我们的单元测试更加有效率。
表格驱动测试方法(Table Driven Tests)
当针对某方法进行单元测试的时候,通常不止写一个测试用例,我们需要测试该方法在多种入参条件下是否都能正常工作,特别是要针对边界值进行测试。通常这个时候表格驱动测试就派上用场了—— 当你发现你在写测试方法的时候用上了复制粘贴,这就说明你需要考虑使用表格驱动测试来构建你的测试方法了 。我们依旧来举个例子:
func TestTime(t *testing.T) { testCases := []struct { // 设计我们的测试用例 gmt string loc string want string }{ {"12:31", "Europe/Zuri", "13:31"}, // incorrect location name {"12:31", "America/New_York", "7:31"}, // should be 07:31 {"08:08", "Australia/Sydney", "18:08"}, } for _, tc := range testCases { // 循环执行测试用例 loc, err := time.LoadLocation(tc.loc) if err != nil { t.Fatalf("could not load location %q", tc.loc) } gmt, _ := time.Parse("15:04", tc.gmt) if got := gmt.In(loc).Format("15:04"); got != tc.want { t.Errorf("In(%s, %s) = %s; want %s", tc.gmt, tc.loc, got, tc.want) } } } 复制代码
表格驱动测试方法让我们的测试方法更加清晰和简练,减少了复制粘贴,并大大提高的测试代码的可读性。
还记得上文说单元测试也是需要维护的吗?单元测试也是代码的一部分,也应当被认真对待。记得要用表格驱动测试的方法来组织你的测试用例,同时别忘了像正式代码那样,写上相应的注释。 更多参考: github.com/golang/go/w… blog.golang.org/subtests
使用测试框架——gomock
What is gomock?
gomock是Google开源的golang测试框架。或者引用官方的话来说:“GoMock is a mocking framework for the Go programming language”。
Why gomock?
上篇文章末尾介绍了mock和stub相结合的测试方法,可以感受到mock与stub结合起来功能固然强大——调用顺序检测,调用次数检测,动态控制函数的返回值等等,但同时,其带来的维护成本和复杂度缺是不可忽视的,手动维护这样一套测试代码那将是一场灾难。我们期望能用一套框架或者工具,在提供强大的测试功能的同时帮我们维护复杂的mock代码。
How does it work?
gomock通过 mockgen
命令生成包含mock对象的 .go
文件,其生成的mock对象具备mock+stub的强大功能,并将我们从写mock对象中解放了出来:
mockgen -destination foo_mock.go -source foo.go -package foo //mock foo.go里面所有的接口,将mock结果保存到foo_mock.go 复制代码
gomock让我们既能使用mock与stub结合的强大功能,又不需要手动维护这些mock对象,岂不美哉?
举个栗子
在这里我们对gomock的基本功能做一个简单演示:
假设我们的接口定义在 user.go
:
// user.go package user // User 表示一个用户 type User struct { Name string } // UserRepository 用户仓库 type UserRepository interface { // 根据用户id查询得到一个用户或是错误信息 FindOne(id int) (User,error) } 复制代码
通过mockgen在同目录下生成mock文件user_mock.go
mockgen -source user.go -destination user_mock.go -package user 复制代码
然后在该目录下新建 user_test.go
来写我们的测试函数,上述步骤完成之后,我们的目录结构如下:
└── user ├── user.go ├── user_mock.go └── user_test.go 复制代码
设置函数的返回值
// 静态设置返回值 func TestReturn(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() repo := NewMockUserRepository(ctrl) // 期望FindOne(1)返回张三用户 repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil) // 期望FindOne(2)返回李四用户 repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil) // 期望给FindOne(3)返回找不到用户的错误 repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found")) // 验证一下结果 log.Println(repo.FindOne(1)) // 这是张三 log.Println(repo.FindOne(2)) // 这是李四 log.Println(repo.FindOne(3)) // user not found log.Println(repo.FindOne(4)) //没有设置4的返回值,却执行了调用,测试不通过 } // 动态设置返回值 func TestReturnDynamic(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() repo := NewMockUserRepository(ctrl) // 常用方法之一:DoAndReturn(),动态设置返回值 repo.EXPECT().FindOne(gomock.Any()).DoAndReturn(func(i int) (*User,error) { if i == 0 { return nil, errors.New("user not found") } if i < 100 { return &User{ Name:"小于100", }, nil } else { return &User{ Name:"大于等于100", }, nil } }) log.Println(repo.FindOne(120)) //log.Println(repo.FindOne(66)) //log.Println(repo.FindOne(0)) } 复制代码
调用次数检测
func TestTimes(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() repo := NewMockUserRepository(ctrl) // 默认期望调用一次 repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil) // 期望调用2次 repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil).Times(2) // 调用多少次可以,包括0次 repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found")).AnyTimes() // 验证一下结果 log.Println(repo.FindOne(1)) // 这是张三 log.Println(repo.FindOne(2)) // 这是李四 log.Println(repo.FindOne(2)) // FindOne(2) 需调用两次,注释本行代码将导致测试不通过 log.Println(repo.FindOne(3)) // user not found, 不限调用次数,注释掉本行也能通过测试 } 复制代码
调用顺序检测
func TestOrder(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() repo := NewMockUserRepository(ctrl) o1 := repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil) o2 := repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil) o3 := repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found")) gomock.InOrder(o1, o2, o3) //设置调用顺序 // 按顺序调用,验证一下结果 log.Println(repo.FindOne(1)) // 这是张三 log.Println(repo.FindOne(2)) // 这是李四 log.Println(repo.FindOne(3)) // user not found // 如果我们调整了调用顺序,将导致测试不通过: // log.Println(repo.FindOne(2)) // 这是李四 // log.Println(repo.FindOne(1)) // 这是张三 // log.Println(repo.FindOne(3)) // user not found } 复制代码
上面的示例只展现了 gomock
功能的冰山一角,在本篇中不再深入讨论,更多用法请参考文档。
更多官方示例: github.com/golang/mock…
如果你完成了上一章的小练习,尝试动手使用gomock改造一下吧!
总结一下
本篇介绍了表格驱动测试与gomock测试框架。运用表格驱动测试方法不仅能使测试代码更精简易读,还能提高我们测试用例的编写能力,无形中提升了单元测试的质量。gomock的功能十分丰富,想掌握各种骚操作还是要细心阅读一下官方示例,但通常20%的常规功能也足够覆盖80%的测试场景了。
表格驱动单元测试和gomock将我们的单元测试效率与质量提升了一个档次。在下一篇文章中,将介绍 testify
断言库,继续优化我们的单元测试。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 搞定Go单元测试(四)—— 依赖注入框架(wire)
- 搞定Go单元测试(二)—— mock框架(gomock)
- 一起来学SpringBoot | 第二十六篇:轻松搞定安全框架(Shiro)
- 快速搞定机器人开发!Facebook 联合 CMU 开源 PyRobot 框架
- 阿里开源首个DL框架,新型XDL帮你搞定大规模稀疏数据
- 轻松搞定RocketMQ入门
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Introduction to Computer Science Using Python
Dierbach, Charles / 2012-12 / $ 133.62
Introduction to Computer Science Using Python: A Computational Problem-Solving Focus introduces students to programming and computational problem-solving via a back-to-basics, step-by-step, objects-la......一起来看看 《Introduction to Computer Science Using Python》 这本书的介绍吧!