Golang语言是现在炙手可热的编程语言之一。每个人可能都或多或少的学习和用Golang在写东西。在程序开发中,测试是一个中必不可少的部分。
有句话工欲善其事,必先利其器,测试就是开发中值得花功夫磨快刀的开发利器。本文我们就一起来学习下如何在Golang测试理念及测试工具。
Golang中附带了用于编写和运行测试工具:标准库testing包以gotest命令行工具运行测试套件。和Golang语言本身的设计理念一致,Golang测试理念很简单:用轻量级的测试包和Golang帮助函数结合实现。在这样的理念下测试也是代码。由于Golang开发人员已经知道如何使用抽象和类型编写Go,因此无需在额外学习特定的测试语法没个人可能都或多或少和配置。看下面一个简单的实例,用testing测试Abs函数简单代码:
funcTestAbs(t*testing.T){
got:=Abs(-1)
ifgot!=1{
t.Errorf(Abs(-1)=%d;want1,got)
}
}
与上面的代码对比,下面是使用Ginkgo库编写的例子。Ginkg库为Golang提供了编写RSpec风格的测试方法:
Describe(Abs,func(){
It(returnscorrectabsvaluefor-1,func(){
got:=Abs(-1)
Expect(got).To(Equal(1))
})
})
两种表达方式,显然第一种方法对Golang码农来说,更加易于理解。RSpec风格中使用诸如函数Describe,Expect的语法模仿了人类语言的表达体验。但是但是对golang码农来说,这来的可能会很突兀,需要重新学习和适应。
另一个相对轻量级的库是testify/assert,它添加了诸如assert.Equal()之类的通用断言函数。
testify/suite则添加了诸如setup和teardown之类的测试套件实用工具。
AwesomeGo网站提供了此类第三方软件包的详尽列表。
还一个不包含在测试包中的有用的测试工具是reflect.DeepEqual(),这是一个标准库函数,它用反射来测试深度相等性,即跟随指针并递归到映射,数组等之后的相等性。当测试比较JSON对象或带有指针的结构之类的东西时,该函数非常有用。以此为基础的两个库是谷歌的go-cmp软件包和DanielNichter的deep,它们类似于DeepEqual,但是格式规范为,更加适应人类阅读的方式。例如,下面使用go-cmp对MakeUsers()函数进行的测试:
其输出更加人性化:
内置测试功能
Golang内置的testing包携带了各种功能,可记录信息和报告故障,在运行时跳过测试或仅以简短模式运行测试。简短模式提供了一种跳过长时间运行或具有大量设置的测试的方法,这在开发过程中可能会有所帮助。可以用-test.short命令行参数启用它。
Go的测试运行程序默认情况下按顺序执行测试,但提供一个可选的Parallel()函数允许跨多个内核同时运行带有显著标记的测试。
在Go1.14中,测试包添加了Cleanup()函数,该函数注册了一个在测试完成时要调用的函数。
这是一种简化拆卸的内置方法,例如,在测试完成后删除数据库表:
Go1.15添加了一个测试助手TempDir(),该助手为当前测试创建(并清理)了一个临时目录。
表驱动测试
Golang中常见的习惯用法是在测试各种边缘情况时避免重复,称为表驱动测试。该技术迭代切片中的测试用例,报告每次迭代的失败:
该t.Errorf()的调用提示存在的问题,但不会停止测试的执行,所以可以报告多个问题。在标准库测试(例如fmt测试)中,这种表驱动测试的样式很常见。Golang1.7引入了一个功能Subtests,它让运行在命令行中单独进行部分子测试,可以更好的控制在失败和平行执行。
模拟和接口
Go的著名语言功能之一是其结构类型化的接口,也被称为编译时鸭子类型=。每当需要在运行时改变行为时,接口就很重要,当然其中包括测试。
例如,正如Golang核心开发者AndrewGerrand在年测试技术演讲的所举过的例子:文件格式解析器不应像这样传递具体的文件类型:
funcParse(f*os.File)error{...}
而,Parse()应该仅采用一个仅实现所需功能的小型接口。在这种情况下,无处不在的io.Reader是一个不错的选择:
funcParse(rio.Reader)error{...}
这样,就可以向解析器提供任何能实现io.Reader的东西,包括文件,字符串缓冲区和网络连接等。它还使测试变得更加容易(可能使用strings.Reader)。
如果测试仅使用大型接口的一小部分,例如来自多个方法API的一种方法,则可以创建一种新的结构类型,该结构类型嵌入接口以实现API合同,并且仅覆盖被调用的方法。。
有各种第三方工具,例如GoMock和mockery,可以从接口定义自动生成模仿代码。
测试用例
Go的软件包文档是从源代码中的注释生成的。与Javadoc或C#的文档系统在代码注释中大量使用标记不同,Golang的方法是,源代码中的注释仍应在源代码中可读,而不不是满篇幅的标记。
它采用与文档示例类似的方法:下面是可运行的代码片段,这些片段在运行测试时自动执行,然后包含在生成的文档中。就像Python的doctests一样,测试用例将写入标准输出,并将输出和期待的输出进行比较,避免文档示例中的回归。这是一个Abs()函数的测试用例:
funcExampleAbs(){
fmt.Println(Abs(15))
fmt.Println(Abs(-24))
//Output:
//15
//24
}
示例函数必须位于*_test.go文件中,并添加Example前缀。当测试运行程序执行时,会自动解析Output:注释并将其与实际输出进行比较,如果它们不同,则测试失败。这些示例作为可运行的GoPlayground代码段包含在生成的文档中,
基准测试
除测试外,该测试包还允许运行定时基准测试。这些在整个标准库中大量使用,以确保执行速度不下降。可以使用带有-bench=选项的gotest自动运行基准测试。
例如,下面标准库的strings.TrimSpace()函数的基准测试用例:
在测试工具将报表中的数字;诸如Benchstat之类的程序可用于比较之前和之后的时间。Benchstat的输出通常包含在Go的提交消息中,这些消息显示了性能的提高。
报告显示,尽管SomeNonASCII子测试的速度降低了约9%,但TrimSpace的ASCII快速路径使纯ASCII输入的速度提高了约5倍。
要诊断某些地方运行缓慢,可以使用内置的profiling性能分析工具,例如运行测试时的-cpuprofile选项。内置的工具pprof以多种格式显示性能结果,包括火焰图。
gotest命令
社区中对Golang对测试目录测试用例(在名为*_test.go的文件中)以及测试函数的命名方式(必须有Test前缀)很有意见。但是,gotest工具确实一个很好用的工具,它可以确切地知道去哪里查找并运行测试用例。不需要额外的用于描述测试存放位置makefile或元数据。如果文件和函数是以标准方式命名的,其他的gotest就可以自动搞定。gotest命令表面很简单,但是其选项不少也很繁琐:
gotest:在当前目录中运行测试
gotestpackage:对给定的包运行测试
gotest./...:对当前目录和所有子包运行测试
gotest-run=foo:运行与正则表达式foo匹配的测试
gotest-cover:运行测试并输出代码覆盖率
gotest-bench=.:#同时运行基准
gotest-bench=.-cpuprofilecpu.out:运行基准测试,记录性能分析信息
gotest的-cover模式会生成代码覆盖率配置文件,可以使用gotoolcover-html=coverage.out将转为html格式。
总结
Go的测试库简单方便,而且可扩展,gotest命令行工具进行测试执行,并进行结果处理、基准测试、性能分析和代码覆盖率等分析。gotesting包中干货满满,需要我们长时间去探索。当然golang也不排除第三测试模块,获取更多的测试体验,当然你也可以开发适合自己的测试模块,所有这些在Golang都是可能。