本文档说明 go 语言自带的测试框架未提供或者未方便地提供的测试方案,主要是用于解决写单元测试中比较头痛的依赖问题。也就是伪造模式,经典的伪造模式有桩对象( stub ),模拟对象( mock )和伪对象( fake )。比较幸运的是,社区有丰富的第三方测试框架支持支持。下面就对笔者亲身试用并实践到项目中的几个框架做介绍:

1.gomock

文档地址:package gomock

gomock 模拟对象的方式是让用户声明一个接口,然后使用 gomock 提供的 mockgen 工具生成 mock 对象代码。要模拟( mock )被测试代码的依赖对象时候,即可使用 mock 出来的对象来模拟和记录依赖对象的各种行为:比如最常用的返回值,调用次数等等。文字叙述有点抽象,直接上代码:

dick.go 中 DickFunc 依赖外部对象 OutterObj,本示例就是说明如何使用 gomock 框架控制所依赖的对象。

func DickFunc( outterObj MockInterface,para int)(result int){fmt.Println("This init DickFunc")fmt.Println("call outter.func:")return outterObj.OutterFunc(para)
}

mockgen工具命令是:

mockgen -source {source_file}.go -destination {dest_file}.go

比如,本示例即是:

mockgen -source src_mock.go -destination dst_mock.go

执行完后,可在同目录下找到生成的 dst_mock.go 文件,可以看到 mockgen 工具也实现了接口:

接下来就可以使用 mockgen 工具生成的 NewMockInterFace 来生产 mock 对象,使用这个 mock 对象。 OutterFunc() 这个函数,gomock在控制mock类时支持链式编程的方式,其原理和其他链式编程类似一直维持了一个Call对象,把需要控制的方法名,入参,出参,调用次数以及前置和后置动作等,最后使用反射来调用方法,所以这个Call对象是mock对象的代理。jmockit的早期版本也是jdk自带的java.reflect.Proxy动态代理实现的(最近的版本是动态Instrumentation配合代理模式)。

在本示例中只简单的更改了返回值,抛砖引玉:

func TestDickFunc(t *testing.T ){mockCtrl := gomock.NewController(t)
//defer mockCtrl.Finish()mockObj := dick.NewMockMockInterface(mockCtrl)mockObj.EXPECT().OutterFunc(3).Return(10)result :=dick.DickFunc(mockObj,3)t.Log("resutl:",result)}

使用go test命令执行这个单测:

从结果看:本来应该输出3,最后输出就是10,和其他语言mock框架相似,生产出来的Mock对象不用自己去重定义这么麻烦。

更多示例可以查看官网一个囊括gomock几乎所有功能的例子:package user

2.httpexcept

由于go在网络架构上的优秀封装,使得go在很多网络场景被广泛使用,而http协议是其中重要部分,在面对http请求的时候,可以对http的client进行测试,算是mock的特殊应用场景。

看一个简单的示例就轻松的看懂了:

func TestHttp(t *testing.T) {handler := FruitServer()server := httptest.NewServer(handler)defer server.Close()e := httpexpect.New(t, server.URL)e.GET("/fruits").Expect().Status(http.StatusOK).JSON().Array().Empty()
}

其中还支持对不同方法(包括Header,Post等)的构造以及返回值Json的自定义,更多细节查看其官网

3.testify

还有一个testify使用起来可以说兼容了《一》中的gocheck和gomock,但是其mock使用稍微有点烦杂,使用继承tetify.Mock(匿名组合)重新实现需要Mock的接口,在这个接口里使用者自己使用Called(反射实现)被Mock的接口。

《单元测试的艺术》中认为stub和mock最大的区别就依赖对象是否和被测对象有交互,而从结果看就是桩对象不会使测试失败,它只是为被测对象提供依赖的对象,并不改变测试结果,而mock则会根据不同的交互测试要求,很可能会更改测试的结果。说了这么多理论,但其实这两种方法都不是割裂的,所以gomock框架除了像其名字一样可以模拟对象以外,还提供了桩对象的功能(stub)。以其实现来说,更像是一个桩对象的注入。但是因为兼容了多个有用的功能,所以其在社区最为火爆。

具体用法可参考其github主页。

4.go-sqlmock

还有一种比较常见的场景就是和数据库的交互场景,go-sqlmock是sql模拟(Mock)驱动器,主要用于测试数据库的交互,go-sqlmock提供了完整的事务的执行测试框架,最新的版本(16.11.02)还支持prepare参数化提交和执行的Mock方案。

比如有这样的被测函数:

func recordStats(db *sql.DB, userID, productID int64) (err error) {tx, err := db.Begin()if err != nil {return}defer func() {switch err {case nil:err = tx.Commit()default:tx.Rollback()}}()if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil {return}if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil {return}return
}func main() {db, err := sql.Open("mysql", "root@/root")if err != nil {panic(err)}defer db.Close()if err = recordStats(db, 1 , 5 ); err != nil {panic(err)}
}

单测时:

func TestShouldUpdateStats(t *testing.T) {db, mock, err := sqlmock.New()if err != nil {t.Fatalf("mock error: '%s' ", err)}defer db.Close()mock.ExpectBegin()mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1))mock.ExpectCommit()if err = recordStats(db, 2, 3); err != nil {t.Errorf("exe error: %s", err)}if err := mock.ExpectationsWereMet(); err != nil {t.Errorf("not implements: %s", err)}
}//测试回滚
func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) {db, mock, err := sqlmock.New()if err != nil {t.Fatalf("mock error: '%s'", err)}defer db.Close()mock.ExpectBegin()mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnError(fmt.Errorf("some error"))mock.ExpectRollback()// 执行被测方法,有错if err = recordStats(db, 2, 3); err == nil {t.Errorf("not error")}// 执行被测方法,mock对象if err := mock.ExpectationsWereMet(); err != nil {t.Errorf("not implements: %s", err)}
}

更多例子和详情,请查看官网:DATA-DOG/go-sqlmock

介绍了这么多框架,最后需要说明的也可能最重要的是写代码时就应该考虑代码是可被测试的。要使得单元测试容易写,或者说代码容易被测,其实很重要的一个部分就是被测代码本身是容易被测的,也就是说在设计和编写代码的时候就应该先想到相好如何单元测试,甚至有人提出可以先写单元测试,再写具体被测代码。因为一个接口(或者称为单元)在被设计好后,它实现就确定了,实际效果也确定了。这种方式被称作测试驱动开发(Test-Driven Development, TDD)。而对于已经写好的代码,很大程度上不好测试,有一种方式是测试性重构,就是为了更好的测试而进行重构。这些一定程度上来说并了解这些框架更重要,有意向可以,可以查阅有关两本书《单元测试的艺术(第2版)》《xUnit测试模式》

参考

《单元测试的艺术 ( 第2版 ) 》 《 xUnit 测试模式 》 如何测试 Go 代码 - 单元测试 Go Testing Toolbox

转载于:

https://cloud.tencent.com/developer/article/1004535

golang 单元测试进阶篇相关推荐

  1. go 单元测试 testing 打印输出_2020,你需掌握go 单元测试进阶篇

    本文说明go语言自带的测试框架未提供或者未方便地提供的测试方案,主要是用于解决写单元测试中比较头痛的依赖问题.也就是伪造模式,经典的伪造模式有桩对象(stub),模拟对象(mock)和伪对象(fake ...

  2. ​手把手教你如何进行 Golang 单元测试

    作者:stevennzhou,腾讯 PCG 前端开发工程师 本篇是对单元测试的一个总结,通过完整的单元测试手把手教学,能够让刚接触单元测试的开发者从整体上了解一个单元测试编写的全过程.最终通过两个问题 ...

  3. 构建测试的体系化思维(进阶篇)

    读完需要 24 分钟 速读仅需 8 分钟 00 引言 1. 三个层次聊测试体系 测试人员缺乏体系化思维?新建产品团队或者新启项目,如何搭建质量保障体系? 大家都接触过不计其数的测试.质量方面的文章或者 ...

  4. 测开 - 进阶篇 - 细节狂魔

    文章目录 回顾上篇博文[测试 - 用例篇](https://blog.csdn.net/DarkAndGrey/article/details/125349067?spm=1001.2014.3001 ...

  5. SpringBoot学习之旅(七)---JPA进阶篇之自定义查询、修改、分页

    文章目录 前言 源码下载 其他文章 查询关键字 自定义Select和Update 分页及自定义分页 自定义分页 分页查询的业务代码 前言 前一节SpringBoot学习之旅(六)-JPA操作MySql ...

  6. Go语言-进阶篇-欧阳桫-专题视频课程

    Go语言-进阶篇-343人已学习 课程介绍         区块链第一语言,Web新贵: 兼具Python的简洁与C++的强大: 用超多好玩的小例子,带你打开通向世界2.0的大门: 风格依旧水煮,依旧 ...

  7. 测试开发学习之旅------进阶篇

    习题课 因果图法 自动饮料的售卖 机,可以输入1.5毛钱或者两块钱硬币,一瓶饮料的价钱1.5 可乐,雪碧,红茶,按哪一种饮料,出哪一种饮料,如果输入2块钱按相应的饮料,出饮料的同时会找出5毛硬币 (1 ...

  8. Enterprise Library Step By Step系列(十二):异常处理应用程序块——进阶篇

    一.把异常信息Logging到数据库 在日志和监测应用程序块中,有朋友提意见说希望能够把异常信息Logging到数据库中,在这里介绍一下具体的实现方法. 1.创建相关的数据库环境: 我们可以用日志和监 ...

  9. Docker 数据卷之进阶篇

    Docker 数据卷之进阶篇 原文:Docker 数据卷之进阶篇 笔者在<Docker 基础 : 数据管理>一文中介绍了 docker 数据卷(volume) 的基本用法.随着使用的深入, ...

  10. C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码...

    原文:C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码 前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github. ...

最新文章

  1. 用Python实现选择排序
  2. 2044. 统计按位或能得到最大值的子集数目
  3. 洛谷P2347 砝码称重 某一年noip提高组原题
  4. Spring Boot 企业实战_前夕
  5. 常引用、常对象和对象的常成员
  6. 2015 圣诞 限免软件分享
  7. 使用Java实现简单串口通信
  8. RTP/RTCP/RTSP/SIP/SDP 关系
  9. 响应式pbootcms模板英文外贸类网站
  10. 解决win10系统alt+tab切换程序不显示程序缩略图问题
  11. “UnsatisfiedDependencyException“的解决方案
  12. 【机器学习笔记】【随机森林】【回归器】【填充缺失值】
  13. Scala基础(四)
  14. Thinkphp资源源码付费下载站网站源码
  15. move_base源码学习
  16. LeetCode 714 买卖股票的最佳时机含手续费
  17. Packet Tracer相关命令
  18. git 基本命令总结
  19. MySQL - 04.数据控制语言(Data Control Language,DCL)
  20. idea简便导入jar包的方法

热门文章

  1. win10系统登录服务器密码存储位置,win10远程服务器登录密码
  2. ELK入门使用-与springboot集成
  3. 赫茨伯格的双因素理论(转载)
  4. 概率论与随机过程(分布函数整合)
  5. 关于“嵌入式系统设计师”的了结。
  6. 深度学习:透过神经网络的内在灵魂与柏拉图的哲学理念
  7. 简述窄带与宽带信号的区别
  8. 北交大远程教育与继续学院计算机答案,北京交通大学远程与继续教育《概率论与数理统计》课后习题答案.docx...
  9. 中国网络游戏行业研究报告-2010
  10. js 获取两个数组的交集,并集,补集,差集(转载+收藏)