【Go高性能】测试(单元测试、基准测试)

news/2024/9/18 20:39:54/ 标签: golang, 单元测试, log4j

Go测试

  • 一、分类
  • 二、基准测试
    • 1. 介绍
    • 2. 基准测试基本原则
    • 3. 使用testing包构建基准测试
      • 3.1 执行基准测试
      • 3.2 基准测试工作原理
      • 3.3 改进基准测试的准确性
        • 3.3.1 -benchtime
        • 3.3.2 -count
        • 3.3.3 -cpu
    • 4. 使用benchstat工具比较基准测试(可跳过)
      • 4.1 对比标准 benchmarks 和 benchstat
    • 5. 避免基准测试的启动耗时
    • 6. 基准测试的内存分配
  • 三、单元测试
    • 1. Goland自动生成
    • 2. 常用断言方法


介绍如何使用 Go 语言的标准库 testing 进行测试。

参考:①https://cloud.tencent.com/developer/article/2211864
②https://geektutu.com/post/quick-go-test.html#7-Benchmark-%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95

一、分类

在Go语言的世界里,testing包是进行单元测试和基准测试的核心组件。它不仅简化了测试流程,还通过简洁明了的API鼓励开发者编写高质量的测试代码。

Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。

1. 单元测试

  • 功能: 测试单个函数/方法,报告测试失败、记录日志、设置测试状态等。
  • 示例: 在 func TestXxx(t *testing.T) 中使用,适用于验证代码的正确性。

2. 基准测试

  • 功能:用于基准测试(性能测试),记录执行时间和内存分配等性能指标。
  • 示例: 在 func BenchmarkXxx(b *testing.B) 中使用,适用于测量代码的性能。

二、基准测试

1. 介绍

要想改进程序的性能,首先要知道程序的当前性能。
benchmark 是 go 语言中用于测试基准性能的工具。该工具用于测试被测试函数的平均运行耗时、内存分配次数。主要适用于在已知性能瓶颈在哪里时的场景。通过对相同功能函数的不同实现的性能指标(平均运行耗时、平均内存分配次数)进行比较,以判断性能的优劣。

2. 基准测试基本原则

为了保证基准测试结果的相对稳定性,需要保持硬件环境的稳定。即:

  • 机器处于空闲状态
  • 机器关闭了节能模式
  • 避免使用虚拟机和云主机

不过如果单纯测试某个函数的性能,精准性要求也不高的话,没必要这么细致啦~

3. 使用testing包构建基准测试

Go的tesing包中内置了基准测试功能。在编写基准测试时基本和编写单元测试的原则相似:

  • 文件名必须以 _test.go 为后缀
  • 函数名必须以 BenchmarkXxxx开头
  • 基准测试函数的参数类型是 *Testing.B,而非 *Testing.T

需要测试函数:

func singleFunc() {time.Sleep(100 * time.Millisecond)
}

基准测试代码:

func Benchmark_singleFunc(b *testing.B) {for n := 0; n < b.N; n++ {singleFunc()}
}

3.1 执行基准测试

  1. 方法一:直接点击方法左侧运行
    在这里插入图片描述
  2. 方法二:使用 go test -bench=要测的方法名 ./方法所在包的相对路径
    例如:go test -bench=singleFunc(已在函数所在包的路径下)
  • -bench 标记使用正则表达式来匹配要运行的基准测试函数名称。所以,最常用的方式是通过-bench . 标记来执行该包下的所有的基准函数。
  • 默认情况下,执行go test命令时 只会执行单元测试,而基准测试会被排除在外。所以,需要在 go test 命令中添加 -bench 标记,以执行基准测试。

基准测试报告每一列值对应的含义如下:

type BenchmarkResult struct {N         int           // 迭代次数T         time.Duration // 基准测试花费的时间Bytes     int64         // 一次迭代处理的字节数MemAllocs uint64        // 总的分配内存的次数MemBytes  uint64        // 总的分配内存的字节数
}

测试结果:

1. goos: windows
2. goarch: amd64
3. pkg: method/f
4. Benchmark_singleFunc
5. Benchmark_singleFunc-6                10         101990650 ns/op
5. PASS

关注指标:

  1. 第5行Benchmark_singleFunc-6 10 101990650 ns/op
  • Benchmark_singleFunc: 这是基准测试的名称,表示测试的函数名。
  • -6: 这是运行基准测试的 goroutine 数量(并发数)。和运行该测试用例时的GOMAXPROCS值有关系,默认为启动时 Go 进程可见的 CPU 数。在这个例子中,6表示使用了 6 个 goroutine 来并行执行基准测试。
  • 10:是基准测试在测试期间执行的总次数(b.N 的值)。
  • 101990650 ns/op:是每次操作的平均耗时,单位是纳秒(ns)。

3.2 基准测试工作原理

每个基准函数被执行时都有一个b.N值,该值是由go运行时自动生成的, 代表基准函数应该执行的次数。

b.N 从 1 开始,基准函数默认要运行 1 秒,如果该函数的执行时间在 1 秒内就运行完了,那么就递增 b.N 的值,再重新再执行一次。

3.3 改进基准测试的准确性

3.3.1 -benchtime

基准测试运行的时间越长,迭代次数越多,最终的平均值结果越准确。

如果你的基准测试只执行了 100 次或 10 次迭代,那么最终得出的平均值可能会偏高。如果你的基准测试执行了上百万或十亿次迭代,那么得出的平均耗时将会非常准确。

可以使用 -benchtime 标识指定基准测试执行的时间以调整迭代次数(即b.N的值),以便得到更准确的结果。例如:

PS E:\Program Data\GoProject\basic\method\f> go test -bench=singleFunc -benchtime=10s
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc-6               100         103430246 ns/op
PASS
ok      method/f        13.644s

执行以上命令,直到其达到 b.N 的值需要花费超过 10 秒的时间才能返回。由于我们的运行时间增加了 10 倍,因此迭代的总次数也增加了 10 倍。结果(每次操作耗时 103430246ns/op) 没有太大的变化,说明我们的数据相对比较稳定,是我们所期望的。
如果你有一个基准测试运行了数百万次或数十亿次迭代,你可能会发现基准值不稳定,因为你的机器硬件的散热性能、内存局部性、后台进程、gc 等因素都会影响函数执行的时间。

3.3.2 -count

通过 -count 标志,可以指定基准测试跑多次,以消除上述的不稳定因素:

 go test -bench . -benchtime 2s -count 5 
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc-6                22         103056855 ns/op
Benchmark_singleFunc-6                21         102299486 ns/op
Benchmark_singleFunc-6                22         104436250 ns/op
Benchmark_singleFunc-6                21         102005167 ns/op
Benchmark_singleFunc-6                21         102501433 ns/op
PASS
ok      method/f        14.764s
3.3.3 -cpu
go test -bench . -cpu 1,2,4            
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc                  10         102037000 ns/op
Benchmark_singleFunc-2                10         101757150 ns/op
Benchmark_singleFunc-4                10         102318930 ns/op
PASS
ok      method/f        6.607s

该示例展示了分别用 CPU 为 1 核、2 核、4 核时运行基准测试的结果。

4. 使用benchstat工具比较基准测试(可跳过)

由于基准测试受电源管理、后台进程、散热的影响,所以对于任何一个基准测试来说,运行多次来求平均值是一个非常好的建议。

下面介绍一个由 Russ Cox 编写的工具:benchstat

% go get golang.org/x/perf/cmd/benchstat

benchstat 可以对一组基准测试的结果求平均值,并显示出对应的稳定性。这是函数在使用电池的电脑上执行的基准示例:

% go test -bench . -benchtime 2s -count 5 | tee old.txt
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc-6                22         103056855 ns/op
Benchmark_singleFunc-6                21         102299486 ns/op
Benchmark_singleFunc-6                22         104436250 ns/op
Benchmark_singleFunc-6                21         102005167 ns/op
Benchmark_singleFunc-6                21         102501433 ns/op
PASS
ok      method/f        14.764s% benchstat old.txt
name     time/op
singleFunc-6  102.5ms ± 2%

平均操作耗时是102.5毫秒,并且误差在 +/-2%。

4.1 对比标准 benchmarks 和 benchstat

确定两组基准测试结果之间的差异可能是单调乏味且容易出错的。 Benchstat 可以帮助我们解决这个问题。

提示 : 保存基准运行的输出很有用,但你也可以保存生成它的二进制文件。 为此,请使用-c标志来保存测试二进制文件;我经常将这个二进制文件从.test重命名为.golden。

% go test -c
% mv fib.test fib.golden 

为了比较新版本,我们编译了一个新的测试二进制文件并对它们都进行了基准测试,并使用benchstat对输出进行比较。

% go test -c
% ./fib.golden -test.bench=. -test.count=10 > old.txt
% ./fib.test -test.bench=. -test.count=10 > new.txt
% benchstat old.txt new.txt
name     old time/op  new time/op  delta
Fib20-8  44.3µs ± 6%  25.6µs ± 2%  -42.31%  (p=0.000 n=10+10)

比较基准测试时需要检查三件事

  • 新老两次的方差。1-2% 是不错的, 3-5% 也还行,但是大于5%的话,可能不太可靠。 在比较一方具有高差异的基准时要小心,您可能看不到改进。
  • p值。p值低于0.05是比较好的情况,大于0.05则意味着基准测试结果可能没有统计学意义。
  • 样本不足。benchstat将报告它认为有效的新旧样本的数量,有时你可能只发现9个报告,即使你设置了-count=10。拒绝率小于10%一般是没问题的,而高于10%可能表明你的设置是不稳定的,也可能是比较的样本太少了。

5. 避免基准测试的启动耗时

有时候基准测试每次执行的时候会有一次启动配置耗时。b.ResetTimer() 函数可以用于忽略启动的累积耗时。如下

func BenchmarkExpensive(b *testing.B) {boringAndExpensiveSetup() //启动配置。默认这里的执行时间是被计算在内的b.ResetTimer()for n := 0; n < b.N; n++ {//function under test}
}

在上例代码中,使用 b.ResetTimer() 函数重置了基准测试的计时器

如果在每次循环迭代中,你有一些费时的配置逻辑,要使用 b.StopTimer()b.StartTimer() 函数来暂定基准测试计时器。

func BenchmarkComplicated(b *testing.B) {for n := 0; n < b.N;n++ {b.StopTimer()complicatedSetup()b.StartTimer()//function under test}
}

6. 基准测试的内存分配

内存分配的次数和分配的大小跟基准测试的执行时间相关。在基准测试中有两种方式可以记录并输出内存分配:

  • 在代码中增加 b.ReportAllocs() 函数来告诉 testing 框架记录内存分配的数据。
  • 在go test命令中添加 -benchmem 标识来强制 testing 框架打印出所有基准测试的内存分配次数

方式一:代码中添加 b.ReportAllocs()

func BenchmarkRead(b *testing.B) {b.ReportAllocs()for n := 0; n < b.N; n++ {//function under test}
}

方式二:go test命令中添加 -benchmem标识

%  go test -run=^$ -bench=. -benchmem bufio
goos: darwin
goarch: amd64
pkg: bufio
BenchmarkReaderCopyOptimal-8            13860543                82.8 ns/op            16 B/op          1 allocs/op
BenchmarkReaderCopyUnoptimal-8           8511162               137 ns/op              32 B/op          2 allocs/op
BenchmarkReaderCopyNoWriteTo-8            379041              2850 ns/op           32800 B/op          3 allocs/op
BenchmarkReaderWriteToOptimal-8          4013404               280 ns/op              16 B/op          1 allocs/op
BenchmarkWriterCopyOptimal-8            14132904                82.7 ns/op            16 B/op          1 allocs/op
BenchmarkWriterCopyUnoptimal-8          10487898               113 ns/op              32 B/op          2 allocs/op
BenchmarkWriterCopyNoReadFrom-8           362676              2816 ns/op           32800 B/op          3 allocs/op
BenchmarkReaderEmpty-8                   1857391               639 ns/op            4224 B/op          3 allocs/op
BenchmarkWriterEmpty-8                   2041264               577 ns/op            4096 B/op          1 allocs/op
BenchmarkWriterFlush-8                  87643513                12.5 ns/op             0 B/op          0 allocs/op
PASS
ok      bufio   13.430s

第四列是每次操作的平均内存分配大小,单位是字节(B)。
第五列是每次操作的平均内存分配次数(allocations)。

三、单元测试

单元测试通常放置在与被测试文件同目录下的_test.go文件中。测试函数必须以Test开头,后接被测试函数名,接受一个t *testing.T参数。

1. Goland自动生成

  1. 把鼠标定在要测试的方法上面,右击选Generate,
    在这里插入图片描述
    在这里插入图片描述
  2. 生成测试文件
    在这里插入图片描述
  3. 增加测试数据
    在这里插入图片描述
  4. 运行测试
    可以直接运行全部的测试方案,也可以自己选择想要运行的测试方案。

2. 常用断言方法

  • t.Errort.Fatal:报告错误,后者还会终止测试。
  • t.Logf:记录日志信息。
  • t.Errorf:当条件不满足时,记录错误并继续执行后续测试。

http://www.ppmy.cn/news/1521362.html

相关文章

鸿蒙(API 12 Beta3版)图形【AR Engine简介】 AR引擎服务

AR Engine&#xff08;AR引擎服务&#xff09;是一个用于在HarmonyOS上构建增强现实应用的引擎&#xff0c;提供了运动跟踪、环境跟踪和命中检测等空间计算能力。通过这些能力&#xff0c;您的应用可以实现虚拟世界与现实世界的融合&#xff0c;给消费者提供全新的视觉体验和交…

海康二次开发学习笔记7-流程相关操作

流程相关操作 流程的相关操作包括选择路径,导入流程,导出流程,运行流程等. 在开始前,扩展优化一下写法,供其他地方重复调用. /// <summary>/// 消息显示区显示消息/// </summary>/// <param name"msg"></param>public void AddMsg(string …

Valley14.2免授权php跨境电商在线商城

6Valley 14.2 Nulled – 多供应商电子商务 – 完整的电子商务移动应用程序、Web、卖家和管理面板 后台可自定义收款&#xff0c;和翻译多国语言&#xff0c;中文需要自己对比翻译&#xff01;一般用不到中文。毕竟是跨境电商平台 带商家即时通讯&#xff0c;全套带文档和APP双端…

请解释Java Web中的Filter的作用和使用场景。什么是Java Web中的JSP?请解释其与Servlet的关系及各自优势。

请解释Java Web中的Filter的作用和使用场景。 Java Web中的Filter&#xff08;过滤器&#xff09;是一个重要的组件&#xff0c;它在客户端与服务器端之间扮演着关键角色。Filter的主要作用是在请求到达Servlet或JSP之前&#xff0c;或者响应返回给客户端之前&#xff0c;对请…

数据赋能(197)——开发:数据应用——实施过程、应用特点

实施过程 数据应用的实施过程通常包括以下几个步骤&#xff1a; 明确业务目标和需求&#xff0c;确定需要采集的数据类型和范围&#xff1b;进行数据的收集、清洗和预处理&#xff0c;确保数据的准确性和一致性&#xff1b;利用数据分析工具和方法对数据进行挖掘和分析&#…

Java算法之计数排序(Counting Sort)

简介 计数排序是一种线性时间复杂度的排序算法&#xff0c;它不依赖于元素之间的比较&#xff0c;而是通过统计数组中每个元素出现的次数&#xff0c;然后根据这些统计信息对元素进行排序。这种算法特别适用于整数且整数的范围不是非常大时。 算法步骤 找出数组中的最大值。…

Unity Xcode方式接入sdk

入口 创建 GameAppController 类 继承 UnityAppController 并且在类的实现之前 需要 加 IMPL_APP_CONTROLLER_SUBCLASS(GameAppController)&#xff0c;表明这个是程序的入口。UnityAppController 实现了 UIApplicationDelegate。 可以简单看下 UIApplicationDelegate 的生命周…

【Git】git 从入门到实战系列(四)—— 工作区、暂存区以及版本库 .git 详解

<> 博客简介&#xff1a;Linux、rtos系统&#xff0c;arm、stm32等芯片&#xff0c;嵌入式高级工程师、面试官、架构师&#xff0c;日常技术干货、个人总结、职场经验分享   <> 公众号&#xff1a;嵌入式技术部落   <> 系列专栏&#xff1a;C/C、Linux、rt…

我们如何看待.NET 8的新功能.NET Aspire?

.NET Aspire是.NET 8中一个非常有前途的新功能&#xff0c;它为开发者提供了一种现代化的方式来构建和部署Web应用程序。通过使用.NET Aspire&#xff0c;开发者可以更快地构建出高性能、可扩展和安全的Web应用程序&#xff0c;并利用新的特性和工具来简化开发过程和提高应用程…

python开发VTK入门

首先用pip命令安装VTK的python库&#xff1b; 需要一些时间&#xff0c;安装完成如下&#xff1b; 基本示例代码&#xff0c; import vtkcube vtk.vtkCubeSource() cube.SetXLength(100.0) cube.SetYLength(200.0) cube.SetZLength(300.0)mapper vtk.vtkPolyDataMapper() ma…

Docker占用根目录/存储空间过多如何清理?

问题背景 使用df -h查看磁盘空间时发现根目录空间不多了&#xff0c;已使用96%&#xff0c;红色警告&#xff01;&#xff01;&#xff01; 于是使用df -h /* 一层一层定位&#xff0c;终于找到了一个大文件 9G多的文件夹&#xff0c;位置是&#xff1a; /var/lib/docker/o…

双向链表的学习

双向链表是一种数据结构&#xff0c;它由节点组成&#xff0c;每个节点包含两个指针&#xff1a;一个指向前一个节点&#xff0c;另一个指向后一个节点。这种结构允许双向访问&#xff0c;即既可以向前遍历&#xff08;像单链表一样&#xff09;&#xff0c;也可以向后遍历。这…

Windows中jupyter开启远程连接(局域网)

我是用实验室的电脑&#xff08;做服务器&#xff0c;window&#xff09;给我的电脑&#xff08;做客户机&#xff0c;window&#xff09;开远程连接&#xff0c;step1-4都是在服务器上操作&#xff0c;step5是在客户机上 step1&#xff1a;生成密钥 服务器cmd输入 jupyter n…

WPF 中常用 `Transform` 类的介绍、使用示例和适用场景

WPF 中常用 Transform 类的介绍、使用示例和适用场景 使用场景解释代码示例示例代码解释 Transform 类描述使用示例适用场景TranslateTransform用于沿 X 轴或 Y 轴平移&#xff08;移动&#xff09;元素。xml <TranslateTransform X"50" Y"100" />移…

什么是前端工程化

前端工程化&#xff08;Front-end Engineering&#xff09;是指通过一系列工具、流程和技术手段&#xff0c;将前端开发工作进行系统化、规范化、自动化的过程。其目的是提高前端开发效率、保证代码质量、降低维护成本&#xff0c;并促进团队协作。前端工程化主要包括以下几个方…

认知升级:互联网行业中的变革引擎与团队潜能激发

一、认知升级在互联网行业的独特价值 互联网行业以其快速迭代、信息爆炸、技术创新为特点&#xff0c;对从业者提出了更高的要求。认知升级&#xff0c;作为个人成长的重要路径&#xff0c;在互联网领域展现出无可替代的价值&#xff1a; 快速适应与学习&#xff1a;互联网行业…

vue2踩坑记录:el-select如何绑定对象

页面上的下拉框&#xff1a; 选中人员之后&#xff0c;需要使用人员的其它信息做后续操作。所以不能只绑定用户Id&#xff0c;需要绑定整个item(用户对象)&#xff0c;这样每次change事件所获取到的newValue是整个对象&#xff0c;而且v-model中的变量指向的也是整个对象&#…

国内六大知名云手机品牌排行榜

如果你希望进行批量操作控制&#xff0c;但又不想投资大量实体设备&#xff1b;如果你想24小时畅玩手游&#xff0c;却又担心设备发烫和高耗电问题&#xff1b;或者你需要在社媒运营和移动办公中使用多个账号&#xff0c;但又不想被实体机束缚……那么云手机将是你的理想选择。…

【MySQL】初识MySQL—MySQL是啥,以及如何简单操作???

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解关于MySQL的简单使用和注意事项&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/wwaqe &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 目…

【Hot100】LeetCode—34. 在排序数组中查找元素的第一个和最后一个位置

目录 1- 思路二分 - 左侧二分 右侧二分 2- 实现⭐34. 在排序数组中查找元素的第一个和最后一个位置——题解思路 3- ACM 实现 原题链接&#xff1a;34. 在排序数组中查找元素的第一个和最后一个位置 1- 思路 二分 - 左侧二分 右侧二分 右区间二分 ——> 找首次出现的位置…