go 单元测试基本篇

作者:熊训德

go 语言本身内置了一套相对轻量级的测试框架,通过 testing 库和 go test 命令支持单元测试。本篇文档主要介绍使用 go 语言 testing 包进行单元测试方法,以及一些在编写单元测试过程遇到的坑。

在 go 中使用单元测试时,在同需测试的源文件目录下增加XXX_test.go(XXX是源文件名)文件即可。如用Intelij IDEA还会帮你把包设置为XXX_test(XXX是源包名),这样可以防止测试依赖的包时的相互依赖,go把这个称作扩展测试包(External test packages)。

下面举一个栗子就很容易理解:

需要测试的函数是一个查询函数,它的功能是通过http协议向异构的某个模块查询信息。因为对方这个模块的接口是可能变更的,所以使用在这个公共函数中使用单元测试不仅可以测试当前代码逻辑,也可帮助往后再开发时的效率。

被测源代码如下:

可以看到被测的公共函数就只有一个入参,两个出参,还算比较简单。

利用go自带框架,最简单的单测代码即可如下:

然后在同目录下输入命令

$go test -v XXX_test.go

其中,-v选项可用于打印每个测试函数的名字和运行时间。

正常情况下,是会出现单测成功或者失败的信息。但这个例子有点不一样,终端会出现这样的错误:

错误里提及component.url配置项并未设置,这是因为这个例子需要依赖外部配置文件,配置文件有设置component.url。

这里,可以使用-o选项。在某些依赖配置文件的情况下-o很有用

go test命令会先生成一个测试可执行文件,如果没-o会是一个临时目录(/tmp/go-build266773839/command-line-arguments),然后在这个临时目录下执行测试的可执行文件,如果使用-o选项:

可以看到可执行文件就会指定在生成可执行测试文件的地方执行(也即是-o指定的目录下执行)。

从这个示例中可以看到,testing包提供了Logf和Error等方法来帮助提高测试效率,如果是Error方法的分支被执行了,则这组测试示例会失败,Logf则是在标准输出上输出log信息。

示例到这里,单测代码也写好了,单测执行也成功了,是否意味着单测就完成。其实不是,个人认为单测的目的主要在于在最小的模块内覆盖所有可能逻辑测试,使得更复杂的模块集成后降低bug的风险。要覆盖被测模块中更多的代码,则需要更多的参数组(测试用例)。实现这个最简单的方法就是多写几个TestXXX,go语言提供了表格驱动的测试方式,把所有测试数据合并到了一个测试中的表格中再集中测试。

go语言中所谓的表格驱动,就是把输入和预期输出做成一个表格,很容易向表格添加新的测试数据,并且后面的测试逻辑也没有冗余,这样我们可以有更多的精力地完善错误信息,比如上例中单元测试是写成类似形式:

tests结构即是测试表格,这样即可以测试是否和预期的输入及输出一致。但是因为本示例中被测函数中的返回值复杂,为了简化单元测试(返回的error不易比对),最终的单元测试是使用t.Logf来查看:

func TestQueryClusterVip(t *testing.T){
    darwinService := component.NewDarwinService(9)
        //测试表格
    var tests = []struct {
        input int64
        rsp *QueryClusterGroupVipRsp
        err error
    }{
        {900086,&QueryClusterGroupVipRsp{Vip:"10.66.188.221"},nil},
        {0,nil,nil},
    }

    for _,test := range tests{
        rsp,err := darwinService.QueryClusterVip(test.input)
        t.Logf("rsp:%v",rsp)
        t.Logf("err:%v",err)
    }
}

go自带的测试框架还提供了测试覆盖率和基准测试两个工具帮助查看编写代码和单测的质量和效率,详见

如果使用惯了了xUnit(JUnit、CppUnitdeng)的单元测试,开始使用go自带的测试框架,会觉得怪怪的,心里满是疑问,为哈单测连都没有断言,表示不服,但是开源社区的gocheck提供了。像Assert简直新手拈来:

func Test(t *testing.T) { TestingT(t) }

type MyTest struct{}

var _ = Suite(&MyTest{})

func (s *Test) TestHelloWorld(c *C) {
    c.Assert(42, Equals, "42")
    c.Assert(io.ErrClosedPipe, ErrorMatches, "io: .*on closed pipe")
    c.Check(42, Equals, 42)
}

这样让单测可以看起来简洁不少,更详细功能和用法可查看其官网

一般来说使用go语言自带的测试框架可以使用单测覆盖60%以上的代码。如果代码中并没有自带http请求,那么测试的时候就需要自己编写代码去构造http请求,。还有一种很常见的场景就是需要测试的代码中依赖了外部资源,而这些外部资源不像本例单测环境里不能提供或者提供特别繁琐,比如网络资源、DB资源等,就需要使用其他测试框架中常用的mock,coverage等技术,而这些go自带测试框架并不提供。

下一篇将介绍以伪对象(Mock,Stub等)核心技术为主的第三方测试框架来补充,以拟补go语言自带的测试框架的不足。

相关推荐

go单元测试进阶篇

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

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

伪装在系统PAM配置文件中的同形异义字后门

0x00. 前言 受到FreeBuf早前相关同形异体字攻击文章的启发,故有此文。 目前主流的Linux发行版本都支持Unicode,这也给了利用同形异义字迷惑系...

35090
来自专栏Python数据科学

Python爬虫之模拟登录wechat

不知何时,微信已经成为我们不可缺少的一部分了,我们的社交圈、关注的新闻或是公众号、还有个人信息或是隐私都被绑定在了一起。既然它这么重要,如果我们可以利用爬虫模拟...

3.5K20
来自专栏恰童鞋骚年

《你必须知道的.NET》读书笔记三:体验OO之美

此篇已收录至《你必须知道的.Net》读书笔记目录贴,点击访问该目录可以获取更多内容。

8320
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第二十天 Redis学习【悟空教程】

rpm -e --nodeps java-1.6.0-openjdk-1.6.0.0-1.66.1.13.0.el6.i686

20350
来自专栏IT技术精选文摘

Nginx模块之Upstream解析

Nginx模块一般被分成三大类:handler、filter和upstream。前面的文章系列中,读者已经了解了handler、filter。利用这两类模块,可...

41760
来自专栏程序员的酒和故事

google/protobuf--VS2015编译、使用

本想用google的libphonenumber这个库来进行电话号相关功能,但是看到需要依赖protobuf,反正都是谷歌出品,那就顺便了解学习一下protob...

46660
来自专栏Golang语言社区

Golang学习--GroupCache的使用

groupcache 是 Brad Fitzpatrick 最新的作品,目标在于取代一部分memcached的功能。以官方的说明是:groupcache ...

60890
来自专栏FreeBuf

Oracle Advanced Support系统SQL注入漏洞挖掘经验分享

Oracle Advanced Support系统SQL注入漏洞分析 一年多前我在客户的一个外部环境中执行渗透测试,任何外部环境渗透测试的重要步骤之一就是挖掘出...

30570
来自专栏腾讯云API

腾讯云 API 最佳实践: 善用幂等性

有些开发者问我云服务器“创建实例”接口有一个参数“ClientToken”不知道有什么作用。本文作一个简单的解答。

4.6K150
来自专栏Ryan Miao

Git 工作流的正确打开方式

前言 一直在使用git做版本控制,也一直工作很顺利,直到和别人发生冲突的时候。这才注意到git 工作流并不是那么简单。比如,之前遇到的清理历史。百度到的资料很...

34560

扫码关注云+社区

领取腾讯云代金券