Golang代码测试:一点到面用测试驱动开发

Stella981
• 阅读 653

摘要:TDD(Test Driven Development),测试驱动开发。期望局部最优到全局最优,这个是一种非常不错的好习惯。

了解Golang的测试之前,我们先了解一下go语言自带的测试工具。

go test工具

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

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

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

Golang代码测试:一点到面用测试驱动开发

运行流程

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

单元测试

以下是来自wiki对于单元测试的定义

在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是必须的,但也不坏,这牵涉到项目管理的政策决定。

每个理想的测试案例独立于其它案例;为测试时隔离模块,经常使用stubs、mock[1]或fake等测试马甲程序。单元测试通常由软件开发人员编写,用于确保他们所写的代码符合软件需求和遵循开发目标。它的实施方式可以是非常手动的(透过纸笔),或者是做成构建自动化的一部分。

简单来说,单元测试就是程序员自己对于自己的代码进行测试,而一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

更有一种开发手法,那就是TDD(Test Driven Development),测试驱动开发。期望局部最优到全局最优,这个是一种非常不错的好习惯。

请注意这里的局部最优的,局部,并不是函数内的详细。而是整个函数。甚至是一个类,等等。

因为有些函数内部的最优,并非这个函数的最优。这点我们需要格外的注意。若有兴趣,可了解一下有点关系的贪心算法。

测试函数格式

其中参数t用于报告测试失败和附加的日志信息。

testing.T的拥有的方法如下:

Golang代码测试:一点到面用测试驱动开发

说了这么多,那么我们来实现一个简单的string中的Split函数,并对他进行单元测试,然后我们在剖析代码。了解单元测试的相关规范。

Golang代码测试:一点到面用测试驱动开发

运行结果如下

Golang代码测试:一点到面用测试驱动开发

说明测试成功,本次通过。当然你也可以在Terminal里面直接运行go test,命令,如下所示

Golang代码测试:一点到面用测试驱动开发

温馨提示:关于可能造成运行test不成功原因

直接在split_test.go,运行。

我们或许知道,go是以文件夹的方法来区分项目。所以当前文件,并不能跑到旁边文件中去找到Split,以至于测试失败。或未达到预期效果。

那么正确的打开方式应该是?

在goland中,鼠标右键点击run测试文件所在的文件夹,选择后面第二个 go test projectFileName。

在Terminal中,应在测试文件所在的文件夹的路径中,进行go test [arge...]。

示例看完了,那么我们进行简单的剖析。我们先从函数文件说起,(也就是这里的splits.go)。

1. 不在是package main,而是packge projectFileName

2. 函数名大写,大写意味着公有函数,可支持外部调用

测试文件

1. 文件名为'*_test.go'

2. 不在是package main,而是packge projectFileName

3. 函数名为TestFuncName

基准测试

基准测试函数格式

基准测试就是在一定的工作负载之下检测程序性能的一种方法。基准测试的基本格式如下:

Golang代码测试:一点到面用测试驱动开发

基准测试以Benchmark为前缀,需要一个*testing.B类型的参数b,基准测试必须要执行b.N次,这样的测试才有对照性,b.N的值是系统根据实际情况去调整的,从而保证测试的稳定性。 testing.B拥有的方法如下:

Golang代码测试:一点到面用测试驱动开发

基准测试示例

我们为我们自己写的Split函数编写基准测试如下:

Golang代码测试:一点到面用测试驱动开发

其中BenchmarkSplit:表示对Split函数进行基准测试

BenchmarkSplit-8:数字8表示GOMAXPROCS的值,这个对于并发基准测试很重要

5188407和206ns/op:表示每次调用Split函数耗时203ns

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

Golang代码测试:一点到面用测试驱动开发

112 B/op:表示每次操作内存分配了112字节

3 allocs/op:则表示每次操作进行了3次内存分配!!!

优化后代码如下:

Golang代码测试:一点到面用测试驱动开发

优化后代码如下

Golang代码测试:一点到面用测试驱动开发

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

仅仅小小的一处改动,就引起如此大的性能改变。so good量变产生质变

性能比较函数

上面的基准测试只能得到给定操作的绝对耗时,但是在很多性能问题是发生在两个不同操作之间的相对耗时,比如同一个函数处理1000个元素的耗时与处理1万甚至100万个元素的耗时的差别是多少?再或者对于同一个任务究竟使用哪种算法性能最佳?我们通常需要对两个不同算法的实现使用相同的输入来进行基准比较测试。

性能比较函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用。举个例子如下:

Golang代码测试:一点到面用测试驱动开发

例如我们编写了一个计算斐波那契数列的函数如下:

Golang代码测试:一点到面用测试驱动开发

我们编写的性能比较函数如下:

Golang代码测试:一点到面用测试驱动开发

运行基准测试:

Golang代码测试:一点到面用测试驱动开发

这里需要注意的是,默认情况下,每个基准测试至少运行1秒。如果在Benchmark函数返回时没有到1秒,则b.N的值会按1,2,5,10,20,50,…增加,并且函数再次运行。

最终的BenchmarkFib40只运行了两次,每次运行的平均值只有不到一秒。像这种情况下我们应该可以使用-benchtime标志增加最小基准时间,以产生更准确的结果。例如:

Golang代码测试:一点到面用测试驱动开发

这一次BenchmarkFib40函数运行了50次,结果就会更准确一些了。

使用性能比较函数做测试的时候一个容易犯的错误就是把b.N作为输入的大小,例如以下两个例子都是错误的示范:

Golang代码测试:一点到面用测试驱动开发

本文分享自华为云社区《Golang代码测试(code review)》,原文作者:PayneWu

点击关注,第一时间了解华为云新鲜技术

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
Java将List中的实体按照某个字段进行分组的算法
publicvoidtest(){List<UserlistnewArrayList<();//User实体测试用Stringid,name;//当前测试以id来分组,具体请按开发场景修改list.add(newUse
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
京东云开发者 京东云开发者
5个月前
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这