go 基准测试 找不到函数_基于Golang做测试

本文在实习期间完成并完善,无任何公司机密,仅做语言交流学习之用。

持续更新。


1.Golang的单元测试

Go语言提供了丰富的单测功能。在Go中,我们通常认为函数是最小的可执行单元。本例中使用两个简单的函数:IsOdd和IsPalindrome来进行Go单测的研究。

171457c245794c2982c3088953e5848f.png

在VSCode中,在函数名上点击右键,选择“Go: Generate Unit Tests For Function"即可生成单测文件。

2461b13ac0aa1ebea157539ad8454d1a.png

前往对应的*_test.go,开始以表格化的方式填写测试用例。这里我们每个函数都填5个测试用例:

87616b397052867a8991987dc7abbb20.png

这里name代表的是测试用例的名字,这里建议每个测试用例的名字都唯一,否则你很有可能不知道发生错误的用例到底是哪个。同时我在34行和38行加入了当测试用例不通过时,输出测试用例名字的语句。这样就可以快速定位到测试不通过的测试用例。这里我们故意将第5个测试用例的want写错(32767是奇数,所以本应该返回true的,也就是want应该为true),看看测试工具是怎么报错的。

点击函数名上面那个run test,,这样可以开始执行测试。run test只会运行它所下面一行所声明的测试函数。如果需要测试所有函数可以命令行输入go test或者go test -v。对IsOdd的测试如下:

a72705322326a66bd35c49fe31eb5dae.png

可以看到其实不自己添加用例输出语句,FAIL的时候go的测试工具也会帮你输出到底是哪个用例不通过。所以34行和38行的代码并不是必须的。我们把第五个测试用例修改成正确的,再次运行测试:

140a3dfa4a67b9e9d654ce1a58239245.png

这个时候全部的测试用例就通过了,而且因为这是一种完全只是关心输入输出的测试,并不涉及到函数内部的具体细节,我们称之为“黑盒测试”。

而其中我们还能选择不同的日志等级输出错误信息,单测框架提供的所有日志方法都会结束测试,若只想标记测试用例不通过,请使用t.Fail()。具体的日志方法如表:

方法功能

t.Log()/t.Logf() 打印标准等级日志,同时结束测试

t.Error()/t.Errorf() 打印错误等级日志,同时结束测试

t.Fatal()/t.Fatalf() 打印致命等级日志,同时结束测试

2.Golang的测试覆盖率

Go的测试覆盖率一般指的是测试用例可以触发函数内的多少个分支语句占全部的分支语句的比例,在VSCode中可以以颜色区分的方式来判断当前的测试函数覆盖到了哪些分支,没有覆盖到哪些分支。首先,在测试文件中,右键任意一个测试函数,选择"Go: Toggle Test Coverage In Current Package"来开始进行测试样例覆盖。

39ae399815d1b4e1f03f3dc1373dea27.png

这里我们将返回值应为false的测试用例给去掉,执行测试。测试完成后回到被测试的函数源文件中,可以发现被测到的分支为以墨绿色标记,而没有被测试到的代码分支以红色标记。

77f5455e636f2c3d32e75ba8f6841e8a.png

将测试样例复原后再进行测试,可以发现测试覆盖率达到了100%,同时所有的代码都是墨绿色的了:

1394b972807082566850f56a510492fa.png

3.Golang的基准性能测试

3.1 非并行Golang benchmark

Golang提供了测试函数运行性能的工具,对于所有的函数来说,其性能测试函数都是在前面加Benchmark。我们还是用上面所说的两个函数,来写一下它们的benchmark测试函数(但是我没找到怎么一键生成benchmark的选项):

d921feb916e4bcc3078735bbc32cbca8.png

其中b.ReportAllocs()会报告这个函数的内存使用情况(执行一次方法要申请多少次内存,每次申请需要申请多大的内存),也可以通过指定-benchmem参数来输出所有函数的内存性能。

执行测试命令(或者使用VSCode的那个run benchmark按钮测试单个函数的benchmark):

Linux:
go test -bench=.
go test -bench=. -benchmem # If memory analysis info is required
go test -bench=. -benchtime=3s # If benchmark test time is not 1s, use -benchtime to set it
Windows:
go test -bench="."
go test -bench="." -benchmem # If memory analysis info is required
go test -bench="." -benchtime=3s # If benchmark test time is not 1s, use -benchtime to set it

测试出来的结果如下:

81b4ce139bfed07ebf010a8d645e31b7.png

输出解读:

数据意义

BenchmarkIsOdd-8 以P=8来测试IsOdd的性能

232279942 代表在1s内(如果没有指定-benchtime则默认测试时间为1s)执行了IsOdd 232279942次

5.16 ns/op 代表每执行一次IsOdd所花费的时间为5.16 ns

0B/op 代表每执行一次IsOdd所分配的内存为0B

0 allocs/op 代表每执行一次IsOdd申请分配内存的次数为0次

我们新写一个函数,这个函数涉及到分配内存。我们先写一个AllocFixedArray来申请一个长度固定的数组并循环往里面填写数据,然后再写一个AllocMutableArray来申请一个长度可扩充的数组,使这两个申请数组的长度相同,观察它们的性能:

2e383b19dd4303696eb97b5d49d0ec7a.png

首先对它们做单测,确保代码运行上没有问题,由于这里没有逻辑判断,所以有一个测试用例就够了:

5442d4e1692fef2f9a0015af15f399d9.png

确认结果正确后,写出它们对应的Benchmark函数:

0c80664bb240707734343e2349fe5863.png

然后可以点击run benchmark一个个测,或者直接全部函数都测一下,这里选择全部测试:

e1d23cf8cdfcb8445544a31d7abbd31f.png

可以发现,使用make声明固定长度的内存是没有allocs的,而append底部会在数组长度不足的时候对数组进行扩充,所以会有内存的申请。并且我们可以看到,append因为底层申请了内存,性能大大下降,AllocMutableArray的执行时间是AllocFixedArray的差不多25倍。这个也提示我们,尽量要对数组的大小有一个预先的估计,并申请好一个capacity比较接近最大上限的数组。

3.2 并行Golang benchmark

测试的时候同样可以使用并行的方法去并发测试指定的时间内能执行多少次该方法,其基本语法为:

b

我们试试将执行比较慢的AllocMutableArray()来并发处理,看看会如何:

822e99bde21d6c722f48c72cff1c11ea.png

执行基准测试,得到结果:

10585c6c2473519138311fba014b8661.png

我们可以发现,在P=8并发执行AllocMutableArray之后,执行时间从73.6ns/op降到了14.6ns/op。

3.3 Golang benckmark中的计时器

假设说一个函数在执行之前,要先执行一些外部的初始化操作。而我们如果在go test里面制定了-benchtime选项,它记录的将会是整个Benchmark函数的运行时间。所以我们需要有一种操纵定时器的方法,来获得整个服务精确的运行时间。假设我们的IsOdd,它在执行之前需要睡眠100毫秒,那么我们就可以在执行完睡眠之后,使用重置计数器的方法开始计时。

d4b66acb8a78770e0a7750d536a047d3.png

a5af15f2135762a807f58fdc7c9203a8.png

OK,测试用的总共时间为3.166s。然后我们加上不对初始化进行计时的代码(取消16、18行的注释),重新测一次:

2e5b0fe12ed7368c8ce620ded6174150.png

可以看到测试时间有显著的下降,这说明使用b.StopTimer()后,没有将初始化的时间算在总测试时间内。

方法功能

b.StartTimer() 复原或打开计时器,当Benchmark执行前会首先执行b.StartTimer()

b.StopTimer() 暂停当前计时器

b.ResetTimer() 重置当前计时器的值,go官方说该函数在计时器运行时无效,但我试了一下是有效的。建议先b.StopTimer()后再调用此方法,最后再b.StartTimer()

StartTimer, StopTimer和ResetTimer其实就相当于我们常用的秒表的三个按钮:开始,暂停和复位。当Benchmark函数执行之前,就会自动调用StartTimer。而ResetTimer函数生效的前提是必须先调用StopTimer。通过这样的控制,就可以控制基准测试的计时器,防止一些无关部分的时间被测算进来了。

3.4 Golang benchmark的Profile(性能分析)

golang的benchmark提供了一种输出性能分析的工具,在测试benchmark命令的前提下,加上参数即可,下面提供了三种获取全部基准测试函数不同性能的指令:

test -bench

当获得这些性能文件之后,也会相应地留下一个***.test为文件名的可执行文件。为什么要留下这个测试时候生成的临时程序呢?在生成profile文件的时候,为了减少冗余,生成的文件全部都是不含符号信息的,也就是说其实并没有记录性能条目对应的是哪个函数的性能,所以需要有一个这样的副本程序来记录符号信息。

当我们获得这些文件之后,使用go自带的pprof来查看这些文件所表示的含义,其中-nodecount=10表示仅显示前10个最耗性能的条目:

=

其中***为根据实际需要所替换的字符串,一个名为go-learning的程序的cpu占用情况分析如下图:

da18e46f45e01db6f52cfb588785f7d7.png

可以看到,IsPalindrome的占用时间排第2位,仅次于gc。所以我们可以着手去从这个函数进行优化。

4.Golang的Example测试(样例测试)

样例测试比较像平时在一些算法刷题平台(比如LeetCode)的题目的一些例子,样例测试以Example打头,其逻辑也很简单,就是使用fmt.Println输出该测试用例的返回结果,然后在函数体的末尾使用如图的注释,一一对应每个fmt.Println的输出:

0c5826a68c02445e512d3dcbdaa5d56d.png

17行的output首字母大写小写均可。

如果13~16行输出的结果和18~21行的结果相对应,go test就会PASS,否则就会FAIL,并打印出实际输出和期望输出。

5.Go的Mock方法

5.1 Mock的简介

mock,中文译名为“模仿,假的”,顾名思义就是构建一个模拟对象,来替换掉一些需要在特定环境下触发的服务,使其可以在不修改原服务的前提下达到测试的目的。本文介绍一种是基于gomonkey的函数/变量Mock方法。

5.2 基于gomonkey的函数Mock方法

在使用gomonkey之前,我们要先安装它,输入命令:

go get github.com/agiledragon/gomonkey

并在开头import该包:

import 

假设我们有一个函数IsRest,当调用这个函数的时候,程序会判断一下现在的时间是否已经是下午5点之后,如果是,就返回nil,表示现在是下班时间了。否则返回非nil值,表示现在还没到下班时间。我们先写出这个函数:

d1e4ba6228cd6b25e724b1f5e34187b5.png

那我们测试的时候肯定不可能等到5点再去测这个函数吧?否则这测试不就没法做了。这个时候我们先生成它的单测函数,然后施加mock:

61b54abe756b11b88cd6fea80aea9009.png

其中,108行~114行是对IsRest进行mock的方法,ApplyFunc指的是对函数进行Mock,第二个参数就是要使用的Mock方法。

那我们来执行测试:

42b0589bc9e8925c039732484dda35bc.png

说明在执行测试用例的时候,gomonkey成功地把IsRest方法给mock掉了。

6. 总结

Go语言本身提供了丰富的单元测试和性能测试方法,但是在提供Mock方法上还是略有不足。本文从Gomock, Gomonkey和GoStub出发,总结了一些创建Mock对象的方法。如果对于Go测试有进一步兴趣的,可以去了解GoConvey,GoMonkey,GoStub和GoMock的教程,下面列出了一个作者写的关于这四个测试工具的文章,供读者参考:

GoConvey框架使用指南

https://www.jianshu.com/p/633b55d73ddd​www.jianshu.com

GoMock框架使用指南

https://www.jianshu.com/p/f4e773a1b11f​www.jianshu.com

GoStub框架使用指南

https://www.jianshu.com/p/70a93a9ed186​www.jianshu.com

Monkey框架使用指南

Monkey框架使用指南​www.jianshu.com
ae644f7272f8893f28bb044a798dd64f.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

九齐NY8B072A单片机使用笔记(三)模拟串口RX

因为这款单片机没有硬件串口,所以需要我们自己做软件模拟串口。 用PA3作为RX,因为PA3可以作为外部输入中断EXTI1。 本人首先用轮询的方式查PA3是否从高电平跳变到低电平(起始信号),但是因为还有别的业务逻辑&#xf…

Java RESTful API集成测试

这篇文章将重点介绍为RESTful API(带有JSON有效负载)编写Java集成测试的基本原理和机制。 目的是对技术进行介绍,并为基本正确性编写一些测试。 这些示例将使用最新版本的GitHub REST API。 对于内部应用程序,这种测试通常将在持…

java警惕自增的陷阱

public class proposal{public static void main(String[] args) {int count0;for(int i0;i<10;i){countcount;}System.out.println(count);}}结果输出&#xff1a;0/*步骤一&#xff1a;JMV吧count值&#xff08;其值是0&#xff09;拷贝到临时变量区&#xff1b;步骤二:co…

[LindCode] Binary Tree Postorder Traversal

Binary Tree Postorder Traversal Given a binary tree, return the postorder traversal of its nodes values. Example Given binary tree {1,#,2,3}, 1\2/3return [3,2,1]. Challenge Can you do it without recursion? SOLUTION 1: recursion&#xff1a; 分治法解决之&am…

九齐NY8B072A单片机使用笔记(一)TIMER0定时器

先上代码 //8bit count up , max 0xFF void Ny8b072a_Timer0_Init(void) {PCON1 C_TMR0_Dis; // Disable Timer0//1 * (255 - 5) 250usTMR0 5; // Load 0x00 to TMR0 (Initial Timer0 register)//16M 2T Div8 1usT0MD C_PS0_TMR0 | C_PS0_Div8 ; // Prescaler0 is assign…

python菜鸟教程split_Python split()方法

网页地址解析&#xff1a; #codingutf-8 str"http://www.runoob.com/python/att-string-split.html" print("0:%s"%str.split("/")[-1]) print("1:%s"%str.split("/")[-2]) print("2:%s"%str.split("/"…

金山毒霸垃圾清理

金山毒霸-垃圾清理-单文件封装,清洁洁癖的爱好&#xff01; 实话&#xff0c;金山的软件确实不错。展望金山可以在软件行业&#xff0c;做出让世界都使用的。为国人扛起一片天 下载地址&#xff1a; http://pan.baidu.com/s/1dFa7GdV转载于:https://www.cnblogs.com/xiaochina/…

并发优化–减少锁粒度

在高负载多线程应用程序中&#xff0c;性能非常重要。 开发人员必须意识到并发问题才能获得更好的性能。 当我们需要并发时&#xff0c;我们通常拥有必须由两个或更多线程共享的资源。 在这种情况下&#xff0c;我们有一个竞争条件 &#xff0c;其中只有一个线程&#xff08;在…

Java1.5增加了新特性:可变参数

/*Java 可变参数Java1.5增加了新特性&#xff1a;可变参数&#xff1a;适用于参数个数不确定&#xff0c;类型确定的情况&#xff0c;java把可变参数当做数组处理。注意&#xff1a;可变参数必须位于最后一项。当可变参数个数多余一个时&#xff0c;必将有一个不是最后一项&…

C语言代码规范(十)花里胡哨代码鉴赏

一、宏定义篇 1、作者的目的是防止GPIO口赋值超过1。但是有明显自觉高人一等&#xff0c;瞧不起读者的感觉。 uint8_t not_func(uint8_t sw) {return (sw?1:0); }#define LED1(sw) PA12not_func(sw)修改建议&#xff1a; #define LED1 PA12 #define LED_ON 0 #de…

python-break循环中断

Python break语句&#xff0c;就像在C语言中&#xff0c;打破了最小封闭for或while循环。 break语句用来终止循环语句&#xff0c;即循环条件没有False条件或者序列还没被完全递归完&#xff0c;也会停止执行循环语句。 break语句用在while和for循环中。 如果您使用嵌套循环&am…

正则表达式验证六位数以上数字,符号,字母任意两种混合的密码验证策略

^(?![0-9]$)(?![a-zA-Z]$)(?!([^(0-9a-zA-Z)]|[\(\)])$)([^(0-9a-zA-Z)]|[\(\)]|[a-zA-Z]|[0-9]){6,}$这个正则如果是单独的数字&#xff0c;字符和符号&#xff0c;是不能通过的&#xff0c;少于6位也不行&#xff0c;希望大家可以继续验证正确性吧转载于:https://www.cnbl…

python post请求实例_Python使用requests发送POST请求实例代码

本文研究的主要是Python使用requests发送POST请求的相关内容&#xff0c;具体介绍如下。 一个http请求包括三个部分&#xff0c;为别为请求行&#xff0c;请求报头&#xff0c;消息主体&#xff0c;类似以下这样&#xff1a; 请求行 请求报头 消息主体 HTTP协议规定post提交的数…

Java Micro-Benchmarking:如何编写正确的基准

几个月前&#xff0c;我写了一篇文章比较循环的短索引的性能 。 我问自己关于使用短裤作为循环迭代次数很少的循环的性能。 在Java语言中&#xff0c;所有对整数的操作都是int进行的。 因此&#xff0c;如果我们使用short作为循环索引&#xff0c;则在每次迭代时都将进行类型转…

新唐M031学习笔记(一)定时器基础计数应用

先上代码 void Hw_Timer0_Init(void) {//20:100ms 200:10ms 2000:1ms 20000:100us 200000:10us TIMER_Open(TIMER0, TIMER_PERIODIC_MODE, 200000);/* Update prescale to set proper resolution. */TIMER_SET_PRESCALE_VALUE(TIMER0, 1); /* Enable Timer0 interrupt */TI…

java三元操作符注意

/* 三元操作符的类型务必一致 */ public class proposal_3 {public static void main(String[] args) {int i80;String sString.valueOf(i<90?90:100);String s1String.valueOf(i<90?90:100.0);if(s.equals(s1))System.out.println("s和s1相等&#xff01;"…

缓解口臭可以喝一种水

河南中医学院第一附属医院耳鼻喉科主任医师梅祥胜点评&#xff1a;通常情况下&#xff0c;口臭跟脾胃湿热有关。中医讲&#xff1a;“胃主受纳&#xff0c;脾主运化&#xff1b;胃气主降&#xff0c;使饮食物及 其糟粕得以下行&#xff0c;脾气主升&#xff0c;则饮食物之精华得…

asp.net+mvc+easyui+sqlite 简单用户系统学习之旅(二)—— easyui的简单实用

下面开始在UserManager.Web中利用easyUI构建web。 1. 先删除自带的controllers、models和views&#xff08;里面的shared和web.config可以保存&#xff09;下面的文件 2. 要利用easyUI&#xff0c;首先去网上下载jquery-easyui-1.3.2.zip&#xff0c;同时下载一份EasyUI-1.3.2.…

adc如何获取周期_LOL:千珏拥有ADC最需要的位移和无敌能力,为什么没人用她打下路?...

— 点击蓝字 关注我们 —英雄联盟自国服上线以来&#xff0c;已经陪伴玩家走过了9个年头&#xff0c;目前英雄联盟中的英雄数量已经达到了151位&#xff0c;每一位都各具特色。千珏是一位深受玩家们喜爱的英雄&#xff0c;其在官方英雄的定位中&#xff0c;属于打野英雄&#x…

航顺HK32F030MF4P6 RST作GPIO SWCLK作EXTI5 SWDIO作ADC_AIN0

老习惯&#xff0c;先上代码 void Hw_Input_Chage_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_IOMUX, ENABLE);GPIOMUX->NRST_PIN_KEY (uint32_t)(0x00005AE1); //KEY…