首页 > 其他分享 >搞定Go单元测试(二)—— mock框架(gomock)

搞定Go单元测试(二)—— mock框架(gomock)

时间:2023-03-07 10:13:59浏览次数:44  
标签:FindOne 单元测试 repo Println mock Go gomock user

通过阅读上一篇文章,相信你对怎么做单元测试已经有了初步的概念,可以着手对现有的项目进行改造并开展测试了。学会了走路,我们尝试跑起来,本篇主要介绍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”。

github.com/golang/mock

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断言库,继续优化我们的单元测试。

转载自: https://juejin.im/post/5ce9354b5188252a72407ac5

标签:FindOne,单元测试,repo,Println,mock,Go,gomock,user
From: https://www.cnblogs.com/gongxianjin/p/17187069.html

相关文章

  • 搞定Go单元测试(一)——基础原理
    单元测试是代码质量的保证。本系列文章将一步步由浅入深展示如何在Go中做单元测试。Go对单元测试的支持相当友好,标准包中就支持单元测试,在开始本系阅读之前,需要对标准测试......
  • 搞定Go单元测试(四)—— 依赖注入框架(wire)
    在第一篇文章中提到过,为了让代码可测,需要用依赖注入的方式来构建我们的对象,而通常我们会在main.go做依赖注入,这就导致main.go会越来越臃肿。为了让单元测试得以顺利进行,mai......
  • 搞定Go单元测试(三)—— 断言(testify
    在上一篇,介绍了表格驱动测试方法和gomock测试框架,大大提升了测试效率与质量。本篇将介绍在测试中引入断言(assertion),进一步提升测试效率与质量。为什么需要断言库我们先来......
  • 【MongoDB】移除复制集成员
    1.使用rs.remove()(1)关闭mongodb实例先关闭要移除的mongodb实例db.shutdownServer()(2)连接到复制集的primary节点使用db.hello()查看复制集的信息>db.hello(){......
  • golang 升级 1.16.3 之后,编译报错 missing go.sum entry for module providing packag
    问题现象在开发机上升级到了最新golang1.16.3版本,在为一个基于golang1.13的历史项目添加excel依赖包后gogetgithub.com/360EntSecGroup-Skylar/excelize/v2......
  • google独立站哪家做得好?
    google独立站哪家做得好?答案是:光算科技做外贸独立站很棒。谷歌是全球最受欢迎的搜索引擎之一,而Google独立站则是许多企业和网站所采用的一种高效的网络营销工具。Google独立......
  • Google独立站和与企业官网的区别是什么?
    google独立站和与企业官网的区别是什么?答案是:独立站通过谷歌SEO优化可以更好的获取自然排名的流量。随着互联网的不断发展,企业越来越重视自身网站的建设和优化,而在企业网站......
  • 2023年如何在Google做外贸
    2023年如何在Google做外贸答案是:利用谷歌SEO获取自然流量促进成交。随着全球化和数字化的发展,外贸行业越来越重视互联网的渠道拓展。在Google搜索引擎上做好SEO优化,是吸引国......
  • Google Review评价如何影响搜索排名?是否有助于提升搜索曝光量
    2015年,全球搜索巨头——Google谷歌推出了我的商家GoogleMyBusiness(GMB),并效仿Yelp、Tripadvisor等点评平台,打造属于商家点评生态体系,挖掘更多本地搜索业务的潜力,并在美国......
  • 为什么 Go 语言是后端开发的未来
    近年来,Go语言的流行度迅速增加。Go最初由Google开发,已迅速成为最流行的后端开发语言之一,尤其是在分布式系统和微服务开发方面。在本文中,我们将讨论Go是后端开发未来......