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

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

1.gomock

https://godoc.org/github.com/golang/mock/gomock

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

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

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

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配合代理模式)。 

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

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

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

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

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

https://godoc.org/github.com/golang/mock/sample

2.httpexcept

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

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

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

其中还支持对不同方法(包括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方案。

比如有这样的被测函数:

  1. func recordStats(db *sql.DB, userID, productID int64) (err error) {
  2. tx, err := db.Begin()
  3. if err != nil {
  4. return
  5. }
  6. defer func() {
  7. switch err {
  8. case nil:
  9. err = tx.Commit()
  10. default:
  11. tx.Rollback()
  12. }
  13. }()
  14. if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil {
  15. return
  16. }
  17. if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil {
  18. return
  19. }
  20. return
  21. }
  22. func main() {
  23. db, err := sql.Open("mysql", "root@/root")
  24. if err != nil {
  25. panic(err)
  26. }
  27. defer db.Close()
  28. if err = recordStats(db, 1 , 5 ); err != nil {
  29. panic(err)
  30. }
  31. }

单测时:

  1. func TestShouldUpdateStats(t *testing.T) {
  2. db, mock, err := sqlmock.New()
  3. if err != nil {
  4. t.Fatalf("mock error: '%s' ", err)
  5. }
  6. defer db.Close()
  7. mock.ExpectBegin()
  8. mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
  9. mock.ExpectExec("INSERT INTO product_viewers")
  10. .WithArgs(2, 3)
  11. .WillReturnResult(sqlmock.NewResult(1, 1))
  12. mock.ExpectCommit()
  13. if err = recordStats(db, 2, 3); err != nil {
  14. t.Errorf("exe error: %s", err)
  15. }
  16. if err := mock.ExpectationsWereMet(); err != nil {
  17. t.Errorf("not implements: %s", err)
  18. }
  19. }
  20. //测试回滚
  21. func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) {
  22. db, mock, err := sqlmock.New()
  23. if err != nil {
  24. t.Fatalf("mock error: '%s'", err)
  25. }
  26. defer db.Close()
  27. mock.ExpectBegin()
  28. mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
  29. mock.ExpectExec("INSERT INTO product_viewers")
  30. .WithArgs(2, 3)
  31. .WillReturnError(fmt.Errorf("some error"))
  32. mock.ExpectRollback()
  33. // 执行被测方法,有错
  34. if err = recordStats(db, 2, 3); err == nil {
  35. t.Errorf("not error")
  36. }
  37. // 执行被测方法,mock对象
  38. if err := mock.ExpectationsWereMet(); err != nil {
  39. t.Errorf("not implements: %s", err)
  40. }
  41. }

更多例子和详情,请查看官网:

https://github.com/DATA-DOG/go-sqlmock

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

参考: http://codethoughts.info/go/2015/04/05/how-to-test-go-code/

https://nathany.com/go-testing-toolbox/

http://shinley.com/index.html

《单元测试的艺术》

《xUnit测试模式》

相关阅读:

go单元测试基本篇

【腾讯TMQ】敏捷测试-快速俘虏产品 & 开发


此文已由作者授权腾讯云技术社区发布,转载请注明文章出处,获取更多云计算技术干货,可请前往腾讯云技术社区

原文链接https://www.qcloud.com/community/article/921985001483606833
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~

go单元测试进阶篇的更多相关文章

  1. 2020,你需掌握go 单元测试进阶篇

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

  2. 【SpringBoot】单元测试进阶实战、自定义异常处理、t部署war项目到tomcat9和启动原理讲解

    ========================4.Springboot2.0单元测试进阶实战和自定义异常处理 ============================== 1.@SpringBoot ...

  3. Membership三步曲之进阶篇 - 深入剖析Provider Model

    Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...

  4. idea 插件的使用 进阶篇

    CSDN 2016博客之星评选结果公布    [系列直播]零基础学习微信小程序!      "我的2016"主题征文活动   博客的神秘功能 idea 插件的使用 进阶篇(个人收集 ...

  5. 2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...

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

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

  7. python 面向对象(进阶篇)

    上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...

  8. 最快让你上手ReactiveCocoa之进阶篇

    前言 由于时间的问题,暂且只更新这么多了,后续还会持续更新本文<最快让你上手ReactiveCocoa之进阶篇>,目前只是简短的介绍了些RAC核心的一些方法,后续还需要加上MVVM+Rea ...

  9. SQL Server调优系列进阶篇(查询优化器的运行方式)

    前言 前面我们的几篇文章介绍了一系列关于运算符的基础介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符. ...

随机推荐

  1. Asp.Net MVC学习总结(一)——Asp.Net MVC简单入门

    一.MVC简单入门 1.1.MVC概念 视图(View) 代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML.XML和Applet. 模型(Model) 表示用户对其数 ...

  2. 【安装eclipse, 配置java环境教程】 编写第一个java程序

    写java通常用eclipse编写,还有一款编辑器比较流行叫IJ.这里我们只说下eclipse编写java的前期工作. 在安装eclipse之前要下载java的sdk文件,即java SE:否则无法运 ...

  3. shell-早间学习,每日一点-5

    http://www.cnblogs.com/liuling/p/2013-8-4-01.htmlhttp://www.cnblogs.com/stephen-liu74/category/32665 ...

  4. 从0移植uboot (二) _启动流程分析

    经过了上一篇的配置,我们已经执行make就可以编译出一个uboot.bin,但这还不够,首先,此时的uboot并不符合三星芯片对bootloader的格式要求,其次,此时的uboot.bin也没有结合 ...

  5. xml中的SQL注入

    大家通常知道xml中大部分会导致外部实体注入,但是,xml也会出现SQL注入: 在xml中正常的sql语句写法有两种: 第一: <select id="selectById" ...

  6. Finding distance between two curves

    http://answers.opencv.org/question/129819/finding-distance-between-two-curves/ 问题: Hello, Im trying ...

  7. 学习笔记——Java数字处理类

    1.数字格式化 使用Java.text.DecimalFormat格式化数字,一般使用其中的DecimalFormat类.如: import java.text.DecimalFormat; publ ...

  8. sql server数据库备份压缩拷贝实例

    --数据库备份压缩拷贝实例:前提要安装RAR压缩软件--声明变量declare @day varchar(10),@dbname varchar(20),@filename varchar(100), ...

  9. Spring框架---IOC装配Bean

    IOC装配Bean (1)Spring框架Bean实例化的方式提供了三种方式实例化Bean 构造方法实例化(默认无参数,用的最多) 静态工厂实例化 实例工厂实例化 下面先写这三种方法的applicat ...

  10. 《你不知道的JavaScript》整理(五)——值与原生函数

    一.值 1)数字 JavaScript只有一种数值类型:number(数字),包括"整数"和带小数的十进制数. //数字的语法 a.toExponential(); // &quo ...