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

1.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().Return() result :=dick.DickFunc(mockObj,)
t.Log("resutl:",result) }

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

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

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)。以其实现来说,更像是一个桩对象的注入。但是因为兼容了多个有用的功能,所以其在社区最为火爆。

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, , ); 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(, ))
mock.ExpectExec("INSERT INTO product_viewers")
.WithArgs(, )
.WillReturnResult(sqlmock.NewResult(, ))
mock.ExpectCommit() if err = recordStats(db, , ); 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(, ))
mock.ExpectExec("INSERT INTO product_viewers")
.WithArgs(, )
.WillReturnError(fmt.Errorf("some error"))
mock.ExpectRollback() // 执行被测方法,有错
if err = recordStats(db, , ); err == nil {
t.Errorf("not error")
} // 执行被测方法,mock对象
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("not implements: %s", err)
}
}

介绍了这么多框架,最后需要说明的也可能最重要的是写代码时就应该考虑代码是可被测试的。要使得单元测试容易写,或者说代码容易被测,其实很重要的一个部分就是被测代码本身是容易被测的,也就是说在设计和编写代码的时候就应该先想到相好如何单元测试,甚至有人提出可以先写单元测试,再写具体被测代码。因为一个接口(或者称为单元)在被设计好后,它实现就确定了,实际效果也确定了。这种方式被称作测试驱动开发(Test-Driven Development, TDD)。而对于已经写好的代码,很大程度上不好测试,有一种方式是测试性重构,就是为了更好的测试而进行重构。这些一定程度上来说比了解这些框架更重要。
以上内容就是本篇的全部内容以上内容希望对你有帮助,有被帮助到的朋友欢迎点赞,评论。
如果对软件测试、接口测试、自动化测试、面试经验交流。感兴趣可以关注我,我们会有同行一起技术交流哦。

2020,你需掌握go 单元测试进阶篇的更多相关文章

  1. go单元测试进阶篇

    作者介绍:熊训德(英文名:Sundy),16年毕业于四川大学大学并加入腾讯.目前在腾讯云从事hadoop生态相关的云存储和计算等后台开发,喜欢并专注于研究大数据.虚拟化和人工智能等相关技术. 本文档说 ...

  2. windows系统快捷操作の进阶篇

    上次介绍了windows系统上一些自带的常用快捷键,有些确实很方便,也满足了我们的一部分需求.但是我们追求效率的步伐怎会止步于此?这一次我将会进一步介绍windows上提升效率的方法. 一:运行 打开 ...

  3. Python之路【第十七篇】:Django【进阶篇 】

    Python之路[第十七篇]:Django[进阶篇 ]   Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接 ...

  4. 4、JavaScript进阶篇①——基础语法

    一.认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂亮的页面,但这还不够,它只是静态页面 ...

  5. JavaScript学习笔记 - 进阶篇(1)- JS基础语法

    前言 JavaScript能做什么? 1.增强页面动态效果(如:下拉菜单.图片轮播.信息滚动等) 2.实现页面与用户之间的实时.动态交互(如:用户注册.登陆验证等) JS进阶篇学习什么? 在JavaS ...

  6. Python之路【第十七篇】:Django之【进阶篇】

    Python之路[第十七篇]:Django[进阶篇 ]   Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接 ...

  7. Python之路【第十七篇】:Django【进阶篇】

    Python之路[第十七篇]:Django[进阶篇 ]   Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接 ...

  8. 从零开始学Sketch——进阶篇-b

    从零开始学Sketch——进阶篇 Sketch是一款矢量绘图应用,而矢量绘图无疑是目前进行网页.图标以及界面设计的最好方式. 在初识了Sketch的界面布局和基础工具之后,我们就可以开始进入高阶的Sk ...

  9. Java进阶篇(六)——Swing程序设计(上)

    Swing是GUI(图形用户界面)开发工具包,内容有很多,这里会分块编写,但在进阶篇中只编写Swing中的基本要素,包括容器.组件和布局等,更深入的内容会在高级篇中出现.想深入学习的朋友们可查阅有关资 ...

随机推荐

  1. PHP strncmp() 函数

    实例 比较两个字符串(区分大小写): <?php高佣联盟 www.cgewang.comecho strncmp("Hello world!","Hello ear ...

  2. 【BZOJ1471】不相交路径 题解(拓扑排序+动态规划+容斥原理)

    题目描述 在有向无环图上给你两个起点和终点分别为$a,b,c,d$.问有几种路径方案使得能从$a$走到$b$的同时能从$c$走到$d$,且两个路径没有交点. $1\leq n\leq 200,1\le ...

  3. Python分析「我们为什么这么穷」

  4. 关于import android.support.v4.app.ContextCompat;找不到contextcompat的解决方法

    android迁移到了androidx,那么相关库的import就有问题了,需要转变为androidx的,这里比如 import android.support.v4.app.ContextCompa ...

  5. 机器学习经典算法(进阶篇)——8.KNN

    KNN是通过测量不同特征值之间的距离进行分类.它的的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别.K通常是不大于20的整数. ...

  6. java 判断集合元素唯一的原理

    一 ArrayList的contains方法判断元素是否重复原理 ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素 所比较,从而根据返回的布尔 ...

  7. C#设计模式之7-桥接模式

    桥接模式(Bridge Pattern) 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/401 访问. 桥接模式属于结构 ...

  8. Flutter 打包程序 build android apk

    Step-1 Java 路径 找到java路径, 可使用[flutter doctor -v] Step-2: 进入目录 找到路径后 C:\Program Files\Java\jre1.8.0_23 ...

  9. 编译原理——求解First,Follow,Firstvt和Lastvt集合

    转载地址 http://dongtq2010.blog.163.com/blog/static/1750224812011520113332714/ 学编译原理的时候,印象最深的莫过于这四个集合了,而 ...

  10. 同步博客到cnblogs平台

    缘由 最最开始在csdn写博客,广告太多,平台暗调资源积分,退:后来使用githubpage+jeklly搭建静态博客,感觉不错,回归到安静的敲打环境.emmmm,由于是静态博客项目,虽能最大化自定义 ...