Go语言提供了go test 命令行工具,使用该工具可以很方便的进行测试。

不仅Go语言源码中大量使用go test,在各种开源框架中的应用也极为普遍。

目前go test支持的测试类型有:

  • 单元测试
  • 性能测试
  • 示例测试

1.单元测试

1.1项目结构

项目中单元测试的结构如下:

[GoExpert]
|--[src]
|--[gotest]
|--unit.go
|--unit_test.go

其中,util.go为源代码文件,unit_test.go为测试文件。要保证测试文件以_test.go结尾。

1.2 源代码文件

源代码文件unit.go中包含一个Add()方法,如下所示:

package gotest

// Add 方法用于演示go test使用
func Add(a int, b int) int {
return a + b
}

Add()方法仅提供两数加法,实际项目中不可能出现类似的方法,此处仅供单元测试示例。

1.3 单元测试

测试文件unit_test.go中包含一个测试方法TestAdd(),如下所示:

package gotest_test

import (
"testing"
"gotest"
) func TestAdd(t *testing.T) {
var a = 1
var b = 2
var expected = 3 actual := gotest.Add(a, b)
if actual != expected {
t.Errorf("Add(%d, %d) = %d; expected: %d", a, b, actual, expected)
}
}

通过package语句可以看到,测试文件属于gotest_test包,测试文件也可以跟源文件在同一个包,但常见的做法是创建一个包专用于测试,这样可以使测试文件和源文件隔离。GO源代码以及其他知名的开源框架通常会创建测试包,而且规则是在原包名上加上”_test”。

测试函数命名规则为TestXxx,其中“Test”为单元测试的固定开头,go test只会执行以此为开头的方法。紧跟“Test”是以首字母大写的单词,用于识别待测试函数。

测试函数参数并不是必须要使用的,但”testing.T”提供了丰富的方法帮助控制测试流程。

t.Errorf()用于标记测试失败,标记失败还有几个方法,在介绍testing.T结构时再详细介绍。

1.4 执行测试

命令行下,使用go test命令即可启动单元测试,如下所示:

$ go test
PASS
ok gotest 0.378s # 还可以使用交互的方式启动
$ go test -v unit_test.go
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
unit_test.go:8: 3
PASS
ok command-line-arguments 0.004s

通过打印可知,测试通过,花费时间为0.378s。

1.5 单元测试总结

  • 测试文件名必须以_test.go结尾;
  • 测试函数名必须以TestXxx开始;
  • 命令行下使用go test即可启动测试;

2.性能测试

2.1 项目结构

通常性能测试也称 基准测试, 目录结构如下

[GoExpert]
|--[src]
|--[gotest]
|--benchmark.go
|--benchmark_test.go

其中benchmark.go为源代码文件,benchmark_test.go为测试文件。

2.2 源码文件

源代码文件benchmark.go中包含MakeSliceWithoutAlloc()MakeSliceWithPreAlloc()两个方法,如下所示:

package gotest

// MakeSliceWithPreAlloc 不预分配
func MakeSliceWithoutAlloc() []int {
var newSlice []int for i := 0; i < 100000; i++ {
newSlice = append(newSlice, i)
} return newSlice
} // MakeSliceWithPreAlloc 通过预分配Slice的存储空间构造
func MakeSliceWithPreAlloc() []int {
var newSlice []int newSlice = make([]int, 0, 100000)
for i := 0; i < 100000; i++ {
newSlice = append(newSlice, i)
} return newSlice
}

两个函数分别用来测试切片预分配内存和不分配内存的性能问题。

两个方法都会构造一个容量为100000的切片,所不同的是MakeSliceWithPreAlloc()会预先分配内存,而MakeSliceWithoutAlloc()不预先分配内存,二者理论上存在性能差异,本次就来测试一下二者的性能差异。

2.3 测试文件

测试文件benchmark_test.go中包含两个测试方法,用于测试源代码中两个方法的性能,测试文件如下所示:

package gotest_test

import (
"testing"
"gotest"
) func BenchmarkMakeSliceWithoutAlloc(b *testing.B) {
for i := 0; i < b.N; i++ {
gotest.MakeSliceWithoutAlloc()
}
} func BenchmarkMakeSliceWithPreAlloc(b *testing.B) {
for i := 0; i < b.N; i++ {
gotest.MakeSliceWithPreAlloc()
}
}

性能测试函数命名规则为BenchmarkXxx,其中Xxx为自定义的标识,需要以大写字母开始,通常为待测函数。

testing.B提供了一系列的用于辅助性能测试的方法或成员,比如本例中的b.N表示循环执行的次数,而N值不用程序员特别关心,按照官方说法,N值是动态调整的,直到可靠地算出程序执行时间后才会停止,具体执行次数会在执行结束后打印出来。

2.4 执行测试

命令行下,使用go test -bench=.命令即可启动性能测试,如下所示:

$ go test -bench=.
BenchmarkMakeSliceWithoutAlloc-4 2000 1103822 ns/op
BenchmarkMakeSliceWithPreAlloc-4 5000 328944 ns/op
PASS
ok gotest 4.445s # 可以指定某个测试文件中运行性能测试
$ go test -v -bench=. benchmark_test.go

其中-bench为go test的flag,该flag指示go test进行性能测试,即执行测试文件中符合”BenchmarkXxx”规则的方法。

紧跟flag的为flag的参数,本例表示执行当前所有的性能测试。

通过输出可以直观的看出,BenchmarkMakeSliceWithoutAlloc执行了2000次,平均每次1103822纳秒,BenchmarkMakeSliceWithPreAlloc执行了5000次,平均每次328944纳秒。

从测试结果上看,虽然构造切片很快,但通过给切片预分配内存,性能还可以进一步提升,符合预期。关于原理分析,请参考Slice相关章节。

2.5 是如何工作的

benchmark 用例的参数 b *testing.B,有个属性 b.N 表示这个用例需要运行的次数。b.N 对于每个用例都是不一样的。

那这个值是如何决定的呢?b.N 从 1 开始,如果该用例能够在 1s 内完成,b.N 的值便会增加,再次执行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快。

2.6 性能测试总结

  • 文件名必须以_test.go结尾;
  • 函数名必须以BenchmarkXxx开始;
  • 使用命令go test -bench=.即可开始性能测试;

3. 示例测试(example test)

示例测试平时很少用到,但是在很多流行的开源项目中能经常见到,它的目的是指导开发人员如何通过示例测试代码,衍生出对应功能的代码

3.1 项目结构

项目结构如下

[GoExpert]
|--[src]
|--[gotest]
|--example.go
|--example_test.go

3.2 源码文件

源代码文件example.go中包含SayHello()SayGoodbye()``PrintNames()三个方法,如下所示:

package gotest

import "fmt"

// SayHello 打印一行字符串
func SayHello() {
fmt.Println("Hello World")
} // SayGoodbye 打印两行字符串
func SayGoodbye() {
fmt.Println("Hello,")
fmt.Println("goodbye")
} // PrintNames 打印学生姓名
func PrintNames() {
students := make(map[int]string, 4)
students[1] = "Jim"
students[2] = "Bob"
students[3] = "Tom"
students[4] = "Sue"
for _, value := range students {
fmt.Println(value)
}
}

这几个方法打印内容略有不同,分别代表一种典型的场景:

  • SayHello():只有一行打印输出
  • SayGoodbye():有两行打印输出
  • PrintNames():有多行打印输出,且由于Map数据结构的原因,多行打印次序是随机的。

3.3 测试文件

测试文件example_test.go中包含3个测试方法,于源代码文件中的3个方法一一对应,测试文件如下所示:

package gotest_test

import "gotest"

// 检测单行输出
func ExampleSayHello() {
gotest.SayHello()
// OutPut: Hello World
} // 检测多行输出
func ExampleSayGoodbye() {
gotest.SayGoodbye()
// OutPut:
// Hello,
// goodbye
} // 检测乱序输出
func ExamplePrintNames() {
gotest.PrintNames()
// Unordered output:
// Jim
// Bob
// Tom
// Sue
}

例子测试函数命名规则为ExampleXxx,其中Xxx为自定义的标识,通常为待测函数名称。

三个测试函数分别代表三种场景:

  • ExampleSayHello(): 待测试函数只有一行输出,使用 // OutPut: 检测。
  • ExampleSayGoodbye():待测试函数有多行输出,使用// OutPut: 检测,其中期望值也是多行。
  • ExamplePrintNames():待测试函数有多行输出,但输出次序不确定,使用// Unordered output:检测。

注:字符串比较时会忽略前后的空白字符。

3.4 执行测试

命令行下,使用go test或go test example_test.go命令即可启动测试,如下所示:

3.5 总结

  1. 例子测试函数名需要以Example开头;
  2. 检测单行输出格式为// Output: <期望字符串>
  3. 检测多行输出格式为// Output: \ <期望字符串> \ <期望字符串>,每个期望字符串占一行;
  4. 检测无序输出格式为// Unordered output: \ <期望字符串> \ <期望字符串>,每个期望字符串占一行;
  5. 测试字符串时会自动忽略字符串前后的空白字符;
  6. 如果测试函数中没有Output标识,则该测试函数不会被执行;
  7. 执行测试可以使用go test,此时该目录下的其他测试文件也会一并执行;
  8. 执行测试可以使用go test <xxx_test.go>,此时仅执行特定文件中的测试函数;

go进阶--测试的更多相关文章

  1. javascript进阶——测试和打包分发

    建立一个面向对象的好的代码基础后,为了达到代码重用的目的,通过调试使用适当的测试用例进行测试尤为必要,之后就是打包分发的主题. 一.调试与测试 1.调试 Firebug:包含了错误控制台.调试器.DO ...

  2. JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(7):装配SpringBean·依赖注入装配

    一.依赖注入的三种方式      在实际环境中实现IoC容器的方式主要分为两大类,一类是依赖查找,依赖查找是通过资源定位,把对应的资源查找回来.另一类则是依赖注入.一般而言,依赖注入可分为3中方式: ...

  3. JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(6):Spring IOC容器学习(概念、作用、Bean生命周期)

    一.IOC控制反转概念 控制反转(IOC)是一种通过描述(在Java中可以是XML或者是注解)并通过第三方去生产或获取特定对象的方式. 主动创建模式,责任在于开发者,而在被动模式下,责任归于Ioc容器 ...

  4. Django 强大的ORM之增删改查

    Django orm         Django——orm进阶 测试Django中的orm 表结构: models.py class User(models.Model): name = model ...

  5. JavaScript入门经典(第7版)读书笔记

    断断续续看了十来天,终于看完了,还是学到些东西,这本书还是不错的,各方面都有涉及. 补充了下之前不完善的JS 知识 笔记一般只记必要的东西.‎ Table of Contents 1. JavaScr ...

  6. 【Web】CSS中的浮动float

    CSS中的float 文章目录 CSS中的float 1.float浮动属性 2.float文字环绕图片 3.float浮动的真正原因以及副作用分析 4.清除浮动的四种解决方法 5.实际应用 导航效果 ...

  7. DLL劫持学习及复现

    0x01 dll简介 在Windows系统中,为了节省内存和实现代码重用,微软在Windows操作系统中实现了一种共享函数库的方式.这就是DLL(Dynamic Link Library),即动态链接 ...

  8. python3使用迭代生成器yield减少内存占用

    技术背景 在python编码中for循环处理任务时,会将所有的待遍历参量加载到内存中.其实这本没有必要,因为这些参量很有可能是一次性使用的,甚至很多场景下这些参量是不需要同时存储在内存中的,这时候就会 ...

  9. 聊聊UI自动化的PageObject设计模式

    当我们开发UI自动化测试用例时,需要引用页面中的元素(数据)才能够进行点击(动作)并显示出页面内容.如果我们开发的用例是直接对HTML元素进行操作,则这样的用例无法"应对"页面中U ...

随机推荐

  1. JAVA-Scaneer对象

    Scanner对象 我们可以通过scanner来获取用户的输入 基本语法 Scanner s = new Scanner(System.in); nextLine():输入 import java.u ...

  2. P3203 弹飞绵羊-分块

    P3203 弹飞绵羊-分块 观察数据范围,发现可以分块.只需要处理每个点跳出所在块后的位置和次数即可.目的是为了加速查询并降低修改复杂度. 对于修改,重构整个块内信息即可. 时间复杂度正确的一批 具体 ...

  3. node fs-extra文件操作模块的使用(支持同步、异步)

    用法参见:fs-extra

  4. Windows7/10 防火墙开放Oracle数据库1521端口

    安装Oracle 12C数据库,在局域网中,允许其他电脑访问,则需要开启防火墙的 1521端口 ==================================================== ...

  5. 微软内推常见问题 Q&A

    很高兴,已经成功内推 59 人拿到了微软 offer! 两年前,我就已经写过一篇微软面经,帮助到了不少人: 微软面经分享:如何更好地做好面试准备 在这两年的内推过程中,往往会有不少候选人来问我有关微软 ...

  6. SickOs1.2靶机

    仅供个人娱乐 靶机信息 靶机下载地址:https://www.vulnhub.com/entry/sickos-12,144/一.主机发现  arp-scan -l 二.端口扫描 1. masscan ...

  7. 'Rem EverythingAutoSetup.VBS 安装Everything的VBS脚本 2019年11月25日写

    'Rem EverythingAutoSetup.VBS 安装Everything的VBS脚本 2019年11月25日写 'Rem Everything是voidtools开发的一款本地NTFS文件和 ...

  8. OpenGL学习笔记(五)变换

    目录 变换 向量 向量的运算 向量与标量运算 向量取反 向量加减 求向量长度 向量的单位化 向量相乘 点乘(Dot Product) 叉乘 矩阵 矩阵的加减 矩阵的数乘 矩阵相乘 矩阵与向量相乘 与单 ...

  9. WinUI 3学习笔记(2)—— 用ListView来展示集合

    在WPF的时代,我们多是使用ListBox和ListView来展示,纵向滚动条显示的集合数据.这两个控件的默认样式,以及对触控的支持,已完全落后于时代.他们两个分别长这样,和Win10及Win11的风 ...

  10. JUC学习笔记(六)

    JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ...