前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >go 单元测试进阶篇

go 单元测试进阶篇

原创
作者头像
腾讯云数据库团队
修改于 2017-06-30 08:55:10
修改于 2017-06-30 08:55:10
9K20
代码可运行
举报
运行总次数:0
代码可运行

作者:熊训德

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

1.gomock

文档地址:package gomock

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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配合代理模式)。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的特殊应用场景。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方案。

比如有这样的被测函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
    }
}

单测时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
2 条评论
热度
最新
话不多说,立马尝试
话不多说,立马尝试
回复回复点赞举报
厉害厉害
厉害厉害
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
一文了解一线互联网大厂的 Golang 单测最佳实战经验
Go 单测里面,最常见的就是通过 gomonkey(stub) 打桩或者 mocker(mock) 的模拟来替换掉我们原本的执行逻辑,因此首先我们要对这两种方式有一个比较深入的理解,要理解为何 Go 单测的时候能够替换掉原来的方法!!!
Allen.Wu
2023/03/01
2.6K0
一文了解一线互联网大厂的 Golang 单测最佳实战经验
Go单测系列3—MySQL和Redis测试
这是Go语言单元测试从零到溜系列教程的第2篇,介绍了如何使用go-sqlmock和miniredis工具进行MySQL和Redis的mock测试。
luckpunk
2023/09/10
6870
Go项目实战--数据Dao层代码的单元测试实战
上节课我给大家介绍了怎么给Go项目做单元测试的规划,当然这里仅限于跟咱们课程里的实战项目一样分层架构设计做的还可以的项目哦,要是所有逻辑都耦合在Controller里,那这个规划就不适用了。。。,所有逻辑都耦合在Controller里还做个锤子的单元测试,直接上线让用户给你测(手机系统都能这么干的。。。你们怕啥)
KevinYan
2025/04/23
1010
Go项目实战--数据Dao层代码的单元测试实战
Go 单元测试之Mysql数据库集成测试
sqlmock 是一个用于测试数据库交互的 Go 模拟库。它可以模拟 SQL 查询、插入、更新等操作,并且可以验证 SQL 语句的执行情况,非常适合用于单元测试中。
贾维斯Echo
2024/04/18
2340
Go 单元测试之Mysql数据库集成测试
上次的问题解决啦,重新送上Go ORM 单元测试全流程讲解
在上次发布的文章《在项目里怎么给 GORM 做单元测试》中对 ORM 的 Update 操作的测试中,因为 ORM 库每次做更新操作时,都会针对updated_at字段进行自动更新,导致我们在写Mock的时候没办法精致匹配这个字段的值,这个问题也就作为一个未解决的问题在文章中发布了出去。
KevinYan
2022/05/23
9570
Go语言——测试与性能
​ 作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。使用 Go 语言的测试 框架,可以在开发的过程中就进行单元测试和基准测试。和 go build 命令类似,go test 命 令可以用来执行写好的测试代码,需要做的就是遵守一些规则来写测试。而且,可以将测试无缝 地集成到代码工程和持续集成系统里。
传说之下的花儿
2023/04/16
1.2K0
一文说尽Golang单元测试实战的那些事儿
导语 | 单元测试,通常是单独测试一个方法、类或函数,让开发者确信自己的代码在按预期运行,为确保代码可以测试且测试易于维护。腾讯后台开发工程师张力结合了公司级漏洞扫描系统洞犀在DevOps上探索的经验,以Golang为例,列举了编写单元测试需要的工具和方法,然后针对写单测遇到的各种依赖问题,详细介绍了通过Mock的方式解决各种常用依赖,方便读者在写go语言UT的时候,遇到依赖问题,能够快速找到解决方案。最后再和大家探讨一下关于单元测试上的一些思考。 一、前言 单元测试,通常是单独测试一个方法、类或函数
腾讯云开发者
2021/07/28
1.4K0
极简版抖音项目的实现(2) —— Mock 和单元测试 | 青训营笔记
本文上接 极简版抖音项目的实现 | 青训营笔记,介绍了该项目视频流服务的单元测试代码。
HikariLan贺兰星辰
2023/03/06
6390
Golang 单元测试合集整理,(我最常用 gomonkey)欢迎收藏
无论写什么样的语言,单元测试都是必不可少的,它可以极大的提高我们的代码质量,减少各种低级错误和 bug
阿兵云原生
2023/09/14
2K0
Golang 单元测试合集整理,(我最常用 gomonkey)欢迎收藏
Golang 单元测试详尽指引
文末有彩蛋。 作者:yukkizhang,腾讯 CSIG 专项技术测试工程师 本篇文章站在测试的角度,旨在给行业平台乃至其他团队的开发同学,进行一定程度的单元测试指引,让其能够快速的明确单元测试的方式方法。 本文主要从单元测试出发,对Golang的单元测试框架、Stub/Mock框架进行简单的介绍和选型推荐,列举出几种针对于Mock场景的最佳实践,并以具体代码示例进行说明。 一、单元测试 1. 单元测试是什么 单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向
腾讯技术工程官方号
2020/10/26
4.6K0
Go语言微服务框架 - 5.GORM库的适配sqlmock的单元测试
随着GORM库的引入,我们在数据库持久化上已经有了解决方案。但上一篇我们使用的GORM过于简单,应用到实际的项目中局限性很大。
junedayday
2021/09/24
2.1K0
Go:微服务架构下的单元测试(基于 Ginkgo、gomock 、Gomega)
本文主要使用 Ginkgo[2] 、gomock[3] 、Gomega[4] 工具来实现单元测试,之前不了解的同学,可以先熟悉一下相关文档。
Freedom123
2024/03/29
6070
Go:微服务架构下的单元测试(基于 Ginkgo、gomock 、Gomega)
白话Golang单元测试
最近学习某个 Golang 单元测试的课程,发现其中推荐使用 gomonkey 这种黑科技,让人略感意外,毕竟在软件开发领域,诸如依赖注入之类的概念已经流传了几十年了,本文希望通过一个例子的演化过程,来总结出 Golang 单元测试的最佳实战。
LA0WAN9
2021/12/14
5160
基于sqlmock模拟数据库驱动编写Golang单元测试用例
当前golang开发人员,在编写完成代码后,通常会写对应的单测来保证代码的健壮。对于很多大厂来说,编写单测已经是代码规范的一部分。基于官方提供的gomock框架和mockgen辅助工具就可以满足绝大部分场景,对于不能直接创建的依赖进行mock。但是,当我们编写API接口的时候,往往会对数据库进行操作,那么就需要支持对SQL进行mock的场景。
KunkkaWu
2024/03/25
9210
Golang 单元测试 - 逻辑层
前面我们完成了最麻烦的数据层的单元测试,今天我们来看看单元测试中最容易做的一层,数据逻辑层,也就是我们通常说的 service 或者 biz 等,是描述具体业务逻辑的地方,这一层包含我们业务最重要的逻辑。
LinkinStar
2023/02/22
5230
使用 Gomock 进行单元测试
在实际项目中,需要进行单元测试的时候。却往往发现有一大堆依赖项。这时候就是 Gomock 大显身手的时候了
李海彬
2018/12/24
3.6K0
手把手教你如何进行 Golang 单元测试
点击上方蓝字,发现更多精彩 导语 本篇是对单元测试的一个总结,通过完整的单元测试手把手教学,能够让刚接触单元测试的开发者从整体上了解一个单元测试编写的全过程。最终通过两个问题,也能让写过单元测试的开发者收获单测执行时的一些底层细节知识。 引入 随着工程化开发在司内大力的推广,单元测试越来越受到广大开发者的重视。在学习的过程中,发现网上针对 Golang 单元测试大多从理论角度出发介绍,缺乏完整的实例说明,晦涩难懂的 API 让初学接触者难以下手。 本篇不准备大而全的谈论单元测试、笼统的介绍 Golang
腾讯VTeam技术团队
2021/06/02
1.5K0
优雅的使用Go进行单元测试
现在我们想测试Target函数,但是由于调用的A函数依赖于自己的某个函数,这里就是A调用了rpc接口拉别人接口数据,我们想mockA接口的目标是,想直接拿到A返回的数据即可,直接采用gomock方式,行不通,自己测试了一下,发现要不断的mock 别人接口所依赖的其他接口,非常麻烦,通过注入代码或者后面第三种方式替换函数即可解决。
公众号guangcity
2020/07/20
3K0
Golang 简洁架构实战
由于golang不像java一样有一个统一的编码模式,所以我们和其他团队一样,采用了 Go 面向包的设计和架构分层这篇文章介绍的一些理论,然后再结合以往的项目经验来进行分包:
luozhiyun
2022/05/09
1.2K0
Golang 简洁架构实战
Golang 高质量单元测试之 Table-Driven:从入门到真香
‍ ‍作者:雷畅,腾讯云监控高级工程师 作为一个程序猿 如何在不受外力(领导?)的胁迫下 自觉自愿写单测? 那必然是相信收益 > 成本 单测节省未来修 bug 的时间 > 写单测所花费的时间 为了保证上述不等式成立,强烈建议您考虑 table-driven 方法!table-driven 方法!!table-driven 方法!!!(只说三遍了) 使用 Table-driven 可以快速、无痛写出高质量单测,以降低“我要写单测”这事的心理门槛,最终达到信手拈来、一直写一直爽的神奇效果!(亲测可信) 什么是
腾讯云可观测平台
2022/01/21
1.1K0
相关推荐
一文了解一线互联网大厂的 Golang 单测最佳实战经验
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验