18-Go语言之单元测试

2023-11-14

go test工具

Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法或工具。

go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。

*_test.go文件中有三种类型的函数,单元测试函数,基准测试函数和示例函数。

类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档

go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行,报告测试结果,最后清理测试中生成的临时文件。

测试函数

测试函数的格式

每个测试函数必须导入testing包,测试函数的基本格式(签名)如下:

func TestName(t *testing.T){
    // ...
}

测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头,举几个例子:

func TestAdd(t *testing.T){ ... }
func TestSum(t *testing.T){ ... }
func TestLog(t *testing.T){ ... }

其中参数t用于报告测试失败和附加的日志信息。 testing.T的拥有的方法如下:

func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool
测试函数示例

接下来我们定义一个split的包,包中定义了一个Split的函数,具体实现如下:

package main

import "strings"

func Split(s, seq string) (res []string) {
	i := strings.Index(s, seq)

	for i > -1 {
		res = append(res, s[:i])
		s = s[i+1:]
		i = strings.Index(s, seq)
	}
	res = append(res, s)
	return
}

在当前目录下,我们创建一个split_test.go的测试文件,并定义一个测试函数如下:

import (
	"reflect"
	"testing"
)

func TestSplit(t *testing.T) { //测试函数名必须以Test开头,必须接受一个*testing.T类型的参数
	got := Split("a:b:c", ":")      //程序输出的结果
	want := []string{"a", "b", "c"} //期望的结果

	if !reflect.DeepEqual(want, got) { //因为slice不能直接比较,借助反射包的方法比较
		t.Errorf("excepted: %v, got: %v", want, got)

	}

}

在当前目录下,执行go test命令,可以看到结果:

sh-3.2$ go test
PASS
ok      day08/lock/test 0.659s

一个测试用例有点单薄,我们再编写一个测试使用多个字符切割字符串的例子,在split_test.go中添加如下测试函数:

func TestMoreSplit(t *testing.T) {
	got := Split("abcd", "bc")
	want := []string{"a", "d"}
	if reflect.DeepEqual(want, got) {
		t.Errorf("expected:%v, got:%v", want, got)
	}
}

再次执行go test命令,输出结果如下:

sh-3.2$ go test
--- FAIL: TestMoreSplit (0.00s)
    split_test.go:23: expected:[a d], got:[a cd]
FAIL
exit status 1
FAIL    day08/lock/test 0.330s

我们可以为go test命令添加-v参数,查看测试函数名称和运行时间:

sh-3.2$ go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestMoreSplit
    split_test.go:23: expected:[a d], got:[a cd]
--- FAIL: TestMoreSplit (0.00s)
FAIL
exit status 1
FAIL    day08/lock/test 0.269s

还可以在go test命令添加-run参数,它对应一个正则表达式,只有函数名匹配上的测试函数才会被go test命令执行。

sh-3.2$ go test -v -run="More"
=== RUN   TestMoreSplit
    split_test.go:23: expected:[a d], got:[a cd]
--- FAIL: TestMoreSplit (0.00s)
FAIL
exit status 1
FAIL    day08/lock/test 0.571s

现在我们回过头来解决我们程序中的问题。很显然我们最初的split函数并没有考虑到sep为多个字符的情况,我们来修复下这个Bug:

package split

import "strings"

// split package with a single split function.

// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) (result []string) {
	i := strings.Index(s, sep)

	for i > -1 {
		result = append(result, s[:i])
		s = s[i+len(sep):] // 这里使用len(sep)获取sep的长度
		i = strings.Index(s, sep)
	}
	result = append(result, s)
	return
}

这一次我们再来测试一下,我们的程序。注意,当我们修改了我们的代码之后不要仅仅执行那些失败的测试函数,我们应该完整的运行所有的测试,保证不会因为修改代码而引入了新的问题。

sh-3.2$ go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
ok      day08/lock/test 0.552s

这一次我们的测试都通过了。

测试组

我们还想测试下split函数对中文字符串的支持,我们可以在写一个函数,但是我们也可以使用如下更友好的一种方式来添加更多的应用实例。

func TestSplit(t *testing.T) {
   // 定义一个测试用例类型
	type test struct {
		input string
		sep   string
		want  []string
	}
	// 定义一个存储测试用例的切片
	tests := []test{
		{input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
		{input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
		{input: "abcd", sep: "bc", want: []string{"a", "d"}},
		{input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}},
	}
	// 遍历切片,逐一执行测试用例
	for _, tc := range tests {
		got := Split(tc.input, tc.sep)
		if !reflect.DeepEqual(got, tc.want) {
			t.Errorf("expected:%v, got:%v", tc.want, got)
		}
	}
}

我们通过上面的代码把多个测试用例合到一起,再次执行go test命令。

split $ go test -v
=== RUN   TestSplit
--- FAIL: TestSplit (0.00s)
    split_test.go:42: expected:[河有 又有河], got:[ 河有 又有河]
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s
测试覆盖率

测试覆盖率是你的代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。

Go提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率。例如:

split $ go test -cover
PASS
coverage: 100.0% of statements
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s

从上面的结果可以看到我们的测试用例覆盖了100%的代码。

Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。例如:

split $ go test -cover -coverprofile=c.out
PASS
coverage: 100.0% of statements
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s

上面的命令会将覆盖率相关的信息输出到当前文件夹下面的c.out文件中,然后我们执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。

基准测试

我们为split包中的Split函数编写基准测试如下:

func BenchmarkSplit(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Split("沙河有沙又有河", "沙")
	}
}

基准测试并不会默认执行,需要增加-bench参数,所以我们通过执行go test -bench=Split命令执行基准测试,输出结果如下:

split $ go test -bench=Split
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8        10000000               203 ns/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       2.255s

其中BenchmarkSplit-8表示对Split函数进行基准测试,数字8表示GOMAXPROCS的值,这个对于并发基准测试很重要。10000000203ns/op表示每次调用Split函数耗时203ns,这个结果是10000000次调用的平均值。

我们还可以为基准测试添加-benchmem参数,来获得内存分配的统计数据。

split $ go test -bench=Split -benchmem
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8        10000000               215 ns/op             112 B/op          3 allocs/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       2.394s

其中,112 B/op表示每次操作内存分配了112字节,3 allocs/op则表示每次操作进行了3次内存分配。 我们将我们的Split函数优化如下:

func Split(s, sep string) (result []string) {
	result = make([]string, 0, strings.Count(s, sep)+1)
	i := strings.Index(s, sep)
	for i > -1 {
		result = append(result, s[:i])
		s = s[i+len(sep):] // 这里使用len(sep)获取sep的长度
		i = strings.Index(s, sep)
	}
	result = append(result, s)
	return
}

这一次我们提前使用make函数将result初始化为一个容量足够大的切片,而不再像之前一样通过调用append函数来追加。我们来看一下这个改进会带来多大的性能提升:

split $ go test -bench=Split -benchmem
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8        10000000               127 ns/op              48 B/op          1 allocs/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       1.423s

这个使用make函数提前分配内存的改动,减少了2/3的内存分配次数,并且减少了一半的内存分配。

格式要求

  1. 测试文件的名字 : xx_test.go
  2. 测试函数的名字:TestXxx(t *testing.T)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

18-Go语言之单元测试 的相关文章

  • 蓝桥之顺子日期

    小明特别喜欢顺子 顺子指的就是连续的三个数字 123 456 等 顺子日期指的就是在日期的 yyyymmdd 表示法中 存在任意连续的三位数是一个顺子的日期 例如 20220123 就是一个顺子日期 因为它出现了一个顺子 123 而 202
  • Linux系统之使用Keepalived+Nginx部署高可用Web集群

    Linux系统之使用Keepalived Nginx部署高可用Web集群 一 本次实践介绍 1 1 本次实践简介 1 2 本次实践环境规划 二 Keepalived和Nginx介绍 2 1 Keepalived简介 2 2 Keepaliv

随机推荐

  • 针对java.net.SocketException: Connection reset的问题排查

    针对java net SocketException Connection reset的问题排查 最近在与第三方系统对接接口时 需要用到socket这种方式 在调试过程中 一直出现java net SocketException Conne
  • 钓鱼邮件攻击分析

    北京网际思安科技有限公司麦赛邮件安全实验室 MailSec Lab 研究发布了 2022年全球邮件威胁报告 以下简称 报告 报告数据显示 在2022年 全球每1000个邮箱 平均每月遭受的邮件攻击数量为299 27次 不含垃圾邮件 同比增加
  • VUE+ElementUI+VueDraggable +El-ImageViewer 实现图片批量上传,支持拖拽控制顺序及图片预览

    话不多说 上代码 没安vuedraggable的小伙伴们自行安装一下 npm install vuedraggable 具体实现
  • Windows 启动Hive 提示:Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClien

    Windows 10 启动Hive 提示如下错误信息 2020 07 06 11 51 18 958 WARN metadata Hive Failed to register all functions java lang Runtime
  • 前端开发经验小结

    http被浏览器强制跳转https 本地调试时如果遇到http强制跳转https的情况 需要修改一下浏览器的HSTS域名安全策略 具体可参考这篇文章 如果是在微信开发者工具 参考这篇文章 window open性能问题 window ope
  • 单片机通用Bootloader框架

    通用Bootloader框架 终端控制台预览 flash分区 APP分区固件制作 设置中断向量表 设置flash起始地址 加入升级成功标识写入 生成可烧写bin文件 固件升级与上载 更新固件 上载固件 升级方式 工程下载 最近搞了Bootl
  • Mysql的安装配置教程(详细)

    首先简单概述分为几个步骤 一 下载Mysql 二 安装Mysql 三 验证Mysql安装是否成功 四 配置环境变量 五 验证配置环境变量是否成功 一 下载Mysql 要在Windows或Mac上安装MySQL 首先从MySQL官方网站下载最
  • uniapp中map使用点聚合渲染marker覆盖物

    效果如图 一 什么是点聚合 当地图上需要展示的标记点 marker 过多时 可能会导致界面上 marker 出现压盖 展示不全 并导致整体性能变差 针对此类问题 推出点聚合能力 点聚合官网教程 二 基本用法 template
  • 使用wireshark对HTTPS解密

    最近需要解析HTTPS流量 所以对wireshark的HTTPS解密进行了实测 使用wireshark解密https的方法 方法一 1 在wireshark的首选项中的protocols的tls选项里添加服务器私钥文件 p12文件需要填写密
  • jar包修改编译反编译操作

    1 首先下载一个反编译工具JD GUI 自己用的是这款 2 获取到你要改的jar包文件 3 先把jar包直接解压暂时放在一个目录里 本人准备修改这个文件 4 再把jar包拖进JD GUI进行解码然后保存到另一个文件夹中 jarTest 5
  • C++ 堆内存分配 new delete 简谈

    堆内存 堆区 heap 是内存空间 是区别于栈区 全局数据区和代码区的内存区域 是程序在运行时申请的内存空间 new和delete new和delete是C 专有的操作符 不需要声明头文件 new是用来申请分配堆内存的 delete是用来释
  • 剖析muduo网络库核心代码,重写muduo库

    项目简介 模拟muduo库实现nonnon blocking IO multiplexing loop线程模型的高并发 TCP 服务器模型 开发环境 Centos7 技术栈 C 多线程 socket网络编程 epoll多路转接 项目设计 整
  • 某机字长为32位,存储容量为64MB,若按字节编址.它的寻址范围是多少?

    问题 1 某计算机字长为32位 其存储容量为16MB 若按双字编址 它的寻址范围是多少 2 某机字长为32位 存储容量为64MB 若按字节编址 它的寻址范围是多少 解答 我的方法是全部换算成1位2进制的基本单元来算 先计算总容量 如第一题中
  • telnet端口不通怎么解决(单边不通的方法建议)

    telnet端口不通是大家在检测端口的时候可能会遇到的问题之一 遇到这种状况一般要如何解决呢 这里为各位带来分享 看一下telnet端口不通的解决方式 看一下如何处理吧 telnet端口不通怎么解决 1 开放供应商服务器端口 总是出现由于连
  • The engine “node“ is incompatible with this module. Expected version

    前言 vue项目用了yarn yarn install后报错如下 开始 执行 yarn config set ignore engines true 然后yarn install后成功 结束 在此记录问题 如有需要修改的地方 还请不吝赐教
  • Kubernetes—K8S运维管理

    Kubernetes K8S运维管理 更新中 一 Node管理 1 1 Node的隔离与恢复 1 2 Node 的扩容 二 更新资源对象的Label 三 Namespace 集群环境共享与隔离 3 1 创建Namespace 3 2 定义C
  • [病虫害识别|博士论文]面向农作物叶片病害鲁棒性识别的深度卷积神经网络研究

    文章目录 创新点 文章中的方法 国内外现状 手工设计特征 基于深度特征学习的农作物病害识别研究 基于高阶残差的卷积神经网络的农作物病害识别 结构图 对比方法 基于高阶残差和参数共享反馈的卷积神经网络农作物病害识别方法 结构图 对比方法 基于
  • CSS选择除第一个和最后两个以外的所有子元素 + 结构伪类选择器深度解析

    最近在练习网易严选首页的布局时 发现它的顶部导航栏需求很特殊 第一项和最后两项是没有下拉选择框的 那么问题来了 在写css的时候该怎么使用选择器去达到这样的需求呢 首先先贴一下我最后的解决方案 nav first gt li nth chi
  • 数据库技术之mysql50题

    目录 数据表介绍 数据SQL 练习题 数据表介绍 1 学 表 Student SId Sname Sage Ssex SId 学 编号 Sname 学 姓名 Sage 出 年 Ssex 学 性别 2 课程表 Course CId Cname
  • 18-Go语言之单元测试

    go test工具 Go语言中的测试依赖go test命令 编写测试代码和编写普通的Go代码过程是类似的 并不需要学习新的语法或工具 go test命令是一个按照一定约定和组织的测试代码的驱动程序 在包目录内 所有以 test go为后缀的