Go入门(5):测试和性能

  • A+
所属分类:学习笔记

第九章学习笔记

 

作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。

使用Go语言的测试框架,可以在开发的过程中就进行单元测试和基准测试。

go test命令可以用来执行写好的测试代码,需要做的就是遵守一些规则来写测试。而且,可以将测试无缝地集成到代码工程和持续集成系统里。

一、单元测试

单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。一个场景是正向路经测试;另外一些单元测试可能会测试负向路径的场景。

Go语言里有几种方法写单元测试。

基础测试(basic test)只使用一组参数和结果来测试一段代码。

表组测试(table test)也会测试一段代码,但是会使用多组参数和结果进行测试。

可以使用一些方法来模仿(mock)测试代码需要使用到的外部资源,如数据库或者网络服务器。

1.
规则一:Go语言的测试工具只会认为以_test.go结尾的文件是测试文件。如果没有遵从这个约定,在包里运行go test的时候就可能会报告没有测试文件。一旦测试工具找到了测试文件,就会查找里面的测试函数并执行。

testing包提供了从测试框架到报告测试的输出和状态的各种测试功能的支持

规则二:一个测试函数必须是公开的函数,并且以Test单词开头。不但函数名字要以Test开头,而且函数的签名必须接收一个指向testing.T类型的指针,并且不返回任何值。如果没有遵守这些约定,测试框架就不会认为这个函数是一个测试函数,也不会让测试工具去执行它。
func TestDownload(t *testing.T)
指向testing.T类型的指针很重要。这个指针提供的机制可以报告每个测试的输出和状态。测试的输出格式没有标准要求。使用Go写文档的方式,输出容易读的测试结果。

go test -v(-v表示提供冗余输出).如果执行go test的时候没有加入冗余选项(-v),除非测试失败,否则我们是看不到任何测试输出的。

2、 表组测试
如果测试可以接受一组不同的输入并产生不同的输出的代码,那么应该使用表组测试的方法进行测试。表组测试除了会有一组不同的输入值和期望结果之外,其余部分都很像基础单元测试。测试会依次迭代不同的值,来运行要测试的代码。每次迭代的时候,都会检测返回的结果。这便于在一个函数里测试不同的输入值和条件。

如果以后需要扩展测试,只需要将新的URL和状态码加入表组就可以,不需要改动测试的核心代码。

3、模仿调用
不能总是假设运行测试的机器可以访问互联网。此外,依赖不属于你的或者你无法操作的服务来进行测试,也不是一个好习惯。这两点会严重影响测试持续集成和部署的自动化。如果突然断网,导致测试失败,就没办法部署新构建的程序。
为了修正这个问题,标准库包含一个名为httptest的包,它让开发人员可以模仿基于HTTP的网络调用。

模仿(mocking)是一个很常用的技术手段,用来在运行测试时模拟访问不可用的资源。包httptest可以让你能够模仿互联网资源的请求和响应。

4、测试服务端点
服务端点(endpoint)是指与服务宿主信息无关,用来分辨某个服务的地址,一般是不包含宿主的一个路径。
如果在构造网络API,你会希望直接测试自己的服务的所有服务端点,而不用启动整个网络服务。包httptest正好提供了做到这一点的机制。

5、示例
Go语言很重视给代码编写合适的文档。专门内置了godoc工具来从代码直接生成文档。这个工具的另一个特性是示例代码。示例代码给文档和测试都增加了一个可以扩展的维度。

示例基于已经存在的函数或者方法。我们需要使用Example代替Test作为函数名的开始。

对于示例代码,需要遵守一个规则。示例代码的函数名字必须基于已经存在的公开的函数或者方法。我们的示例的名字基于handlers包里公开的SendJSON函数。如果没有使用已经存在的函数或者方法,这个示例就不会显示在包的Go文档里。

写示例代码的目的是展示某个函数或者方法的特定使用方法。为了判断测试是成功还是失败,需要将程序最终的输出和示例函数底部列出的输出做比较。

二、基准测试

基准测试是一种测试代码性能的方法。

想要测试解决同一问题的不同方案的性能,以及查看哪种解决方案的性能更好时,基准测试就会很有用。基准测试也可以用来识别某段代码的CPU或者内存效率问题,而这段代码的效率可能会严重影响整个应用程序的性能。

许多开发人员会用基准测试来测试不同的并发模式,或者用基准测试来辅助配置工作池的数量,以保证能最大化系统的吞吐量。

基准测试的文件名也必须以_test.go结尾。同时也必须导入testing。

基准测试函数必须以Benchmark开头,接受一个指向testing.B类型的指针作为唯一参数。

基准测试框架默认会在持续1秒的时间内,反复调用需要测试的函数。测试框架每次调用测试函数时,都会增加b.N的值。第一次调用时,b.N的值为1。需要注意,一定要将所有要进行基准测试的代码都放到循环里,并且循环要使用b.N的值。否则,测试的结果是不可靠的。

如果我们只希望运行基准测试函数,需要加入-bench选项
go test-v -run="none"-bench="BenchmarkSprintf"
这次go test调用里,我们给-run选项传递了字符串"none",来保证在运行制订的基准测试函数之前没有单元测试会被运行。这两个选项都可以接受正则表达式,来决定需要运行哪些测试。由于例子里没有单元测试函数的名字中有none,所以使用none可以排除所有的单元测试。

默认情况下,基准测试的最小运行时间是1 秒。你会看到这个测试框架持续运行了大约1.5秒。如果想让运行时间更长,可以使用另一个名为-benchtime的选项来更改测试执行的最短时间。
对大多数测试来说,超过3秒的基准测试并不会改变测试的精确度。只是每次基准测试的结果会稍有不同。

基准测试里面调用b.ResetTimer的作用。
在代码开始执行循环之前需要进行初始化时,这个方法用来重置计时器,保证测试代码执行前的初始化代码,不会干扰计时器的结果。为了保证得到的测试结果尽量精确,需要使用这个函数来跳过初始化代码的执行时间。

运行基准测试时,另一个很有用的选项是-benchmem选项。这个选项可以提供每次操作分配内存的次数,以及总共分配内存的字节数。输出的结果会多出两组新的数值:一组数值的单位是B/op,另一组的单位是allocs/op。单位为allocs/op的值表示每次操作从堆上分配内存的次数。单位为B/op的值表示每次操作分配的字节数。

在运行单元测试和基准测试时,还有很多选项可以用。建议查看一遍所有选项,以便在编写自己的包和工程时,充分利用测试框架。社区希望包的作者在正式发布包的时候提供足够的测试。

三、总结

测试功能被内置到Go语言中,Go语言提供了必要的测试工具。

go test工具用来运行测试。

测试文件总是以_test.go作为文件名的结尾。

表组测试是利用一个测试函数测试多组值的好办法。

包中的示例代码,既能用于测试,也能用于文档。

基准测试提供了探查代码性能的机制。

发表评论

您必须才能发表评论!