作者:熊训德

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单元测试进阶篇

文章来源于腾讯云开发者社区,点击查看原文