Go单元测试及框架使用
Go自带测试框架
单元测试
- 建议Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。
- 函数名必须以 Test 开头,后面一般跟待测试的函数名
- 参数为 t *testing.T
简单测试用例定义如下:
func TestXXXX(t *testing.T) {// ...}
在goland中,编写好方法后,右键Generate->Test for funtion, 可自动生成单元测试代码
生成的代码如下:
需要在TODO里填上单元测试参数,含义如下:
name:单元测试名称args:方法入参want:希望的出参
测试结果:
日志打印
Log() | 打印日志 |
---|---|
Logf() | 格式化打印日志 |
Error() | 打印错误日志 |
Errorf() | 格式化打印错误日志 |
Fatal() | 打印致命日志, 会直接中断当前测试方法 |
Fatalf() | 格式化打印致命日志,会直接中断当前测试方法 |
Fail() | 标记失败,但继续执行当前测试函数 |
FailNow() | 失败,立即终止当前测试函数执行 |
Skip() | 跳过当前函数,通常用于未完成的测试用例 |
基准测试
基准测试用例的定义如下:
func BenchmarkName(b *testing.B){// ...}
- 函数名必须以 Benchmark 开头,后面一般跟待测试的函数名
- 参数为 b *testing.B
- goland中没有自动基准测试的方法,需要按照规则手动自己加
原方法
func sayHi(name string) string{return "hi," + name}
基准测试代码
func BenchmarkSayHi(b *testing.B) {for i := 0; i < b.N; i++ {sayHi("Max")}}
Goland中执行基准测试
命令行中执行基准测试
go test helloworld_test.go
结果解读
当测试开始时,b.N的值被设置为1,执行后如果没有超过默认执行时间上限(默认为1秒),则加大b.N的值,按某种规则一直递增,直到执行时间等于或超过上限,那么就用这一次的b.N的值,做为测试的最终结果
BenchmarkSayHi-12 81593520 14.71 ns/opPASSok zh.com/internal/benchmark_test 2.347s
- BenchmarkSayHi-12表示执行 BenchmarkSayHi 时,所用的最大P的数量为12
- 81593520: 表示sayHi()方法在达到这个执行次数时,等于或超过了1秒
- 14.71 ns/op: 表示每次执行sayHi()所消耗的平均执行时间
- 2.347s:表示测试总共用时
测试总时间的计算
既然81593520表示1秒或大于1秒时执行的次数,那么测试总时间用时却是2.386s,超出了不少,这是为什么呢
在测试中加入b.Log(“NNNNN:”, b.N),再执行基准测试,并加入-v,打印测试中的日志
func BenchmarkSayHi(b *testing.B) {for i := 0; i < b.N; i++ {SayHi("Max")}b.Log("NNNNN:", b.N)
}
go test -v -bench=. -run=^$ gott/SayHiBenchmarkSayHifun1_test.go:26: NNNNN: 1fun1_test.go:26: NNNNN: 100fun1_test.go:26: NNNNN: 10000fun1_test.go:26: NNNNN: 1000000fun1_test.go:26: NNNNN: 3541896fun1_test.go:26: NNNNN: 4832275BenchmarkSayHi-4 4832275 236.8 ns/opPASSok gott/SayHi 2.395s
可以看到b.Log(“NNNNN:”, b.N)被执行了6次,这证明了之前提到的,测试会对b.N依次递增,直到执行时间等于或超过上限。在对BenchmarkSayHi()运行基准测试时,N值依次按1,100,10000,1000000,3541896,4832275递增,直到执行次数为4832275时,执行时间等于或超过了上限。
同时也说明BenchmarkSayHi()一共被调用了6次,每次运行BenchmarkSayHi()都要消耗一定的时间,所以测试总耗时为这6次调用时间之和,2.395s,超过了1秒
benchtime 标记
可以通过-benchtime标记修改默认时间上限,比如改为3秒
go test -v -bench=. -benchtime=3s -run=^$ gott/SayHigoos: darwingoarch: amd64pkg: gott/SayHiBenchmarkSayHifun1_test.go:31: NNNNN: 1fun1_test.go:32: /Users/ga/m/opt/go/go_rootfun1_test.go:31: NNNNN: 100fun1_test.go:32: /Users/ga/m/opt/go/go_rootfun1_test.go:31: NNNNN: 10000fun1_test.go:32: /Users/ga/m/opt/go/go_rootfun1_test.go:31: NNNNN: 1000000fun1_test.go:32: /Users/ga/m/opt/go/go_rootfun1_test.go:31: NNNNN: 15927812fun1_test.go:32: /Users/ga/m/opt/go/go_rootBenchmarkSayHi-4 15927812 223.4 ns/opPASSok gott/hello 3.802s
还可以设置具体的探索次数最大值,格式为-benchtime=Nx
go test gott/hello -run=^$ -bench=BenchmarkHello -benchtime=50xgoos: darwingoarch: amd64pkg: gott/helloBenchmarkHello-4 50 2183 ns/op--- BENCH: BenchmarkHello-4fun1_test.go:35: NNNNN: 1fun1_test.go:36: /Users/ga/m/opt/go/go_rootfun1_test.go:35: NNNNN: 50fun1_test.go:36: /Users/ga/m/opt/go/go_rootPASSok gott/hello 0.011s
b.N的值被设置为50,函数运行了50次
benchmem 标记
可以通过-benchmem标记查看内存使用信息
go test -bench=. -run=none -benchmem
go test gott/hello -run=^$ -bench=BenchmarkHello -benchmem
go test gott/hello -run=^$ -bench=BenchmarkHello -benchmem
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-4 5137456 223.1 ns/op 32 B/op 2 allocs/op
--- BENCH: BenchmarkHello-4
fun1_test.go:35: NNNNN: 1
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 100
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 10000
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 1000000
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 5137456
fun1_test.go:36: /Users/ga/m/opt/go/go_root
... [output truncated]
PASS
ok gott/hello 1.399s
- 32 B/op:平均每次迭代内存分配的字节数
- 2 allocs/op:平均每次迭代内存分配的次数
平均每次迭代计算的依据应该使用的是 b.N=5137456迭代次数
基准测试的用途
一般用于对比两个不同的操作所消耗的时间,如
- 渐近增长函数的运行时间一个函数需要1ms处理1,000个元素,处理10000或1百万将需要多少时间呢
- I/O缓存该设置为多大基准测试可以帮助我们选择在性能达标情况下所需的最小内存
- 确定哪种算法更好
覆盖率测试
运行run with coverage
结果解读
右侧会展示覆盖率,左侧绿色为单元测试已覆盖到的代码,红色为未覆盖的代码
example测试
样例测试比较像平时在一些算法刷题平台(比如LeetCode)的题目的一些例子,样例测试以Example打头,其逻辑也很简单,就是使用fmt.Println输出该测试用例的返回结果,然后在函数体的末尾使用如图的注释,一一对应每个fmt.Println的输出:
如果输出和注释不能对应上则不通过
模糊测试
go版本要求
Fuzz模糊测试需要Go 1.18 Beta 1或以上版本的泛型功能
测试代码
package fuzz_testimport ("fmt""testing""unicode/utf8"
)func Reverse(s string) string {b := []byte(s)for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {b[i], b[j] = b[j], b[i]}return string(b)
}func FuzzReverse(f *testing.F) {testcases := []string{"Hello, world", "!12345"}for _, tc := range testcases {f.Add(tc) // Use f.Add to provide a seed corpus}f.Fuzz(func(t *testing.T, orig string) {rev := Reverse(orig)fmt.Printf("original->:%s", orig)fmt.Printf("after->:%s", rev)doubleRev := Reverse(rev)if orig != doubleRev {t.Errorf("Before: %q, after: %q", orig, doubleRev)}if utf8.ValidString(orig) && !utf8.ValidString(rev) {t.Errorf("Reverse produced invalid UTF-8 string %q", rev)}})
}
测试结果
第三方框架
总体介绍
框架名 | 使用说明 | 优点 | 缺点 |
---|---|---|---|
testing | 如上 | go官方原生测试框架,简单好用 | 断言不够友好,需要大量if else可以配合testify的assert使用 |
testify | 1. 和 go test 无缝集成,直接使用该命令运行2. 支持断言,写法更简便3. 支持 mock & suite功能 | mock的功能不够强大,需要配合其他mock框架使用 | |
GoConvey | 1. 能够使用 go test 来运行测试2. 支持断言,写法更简便3. 支持通过浏览器查看测试结果4. 支持嵌套,可以分组 | 1. 写法并不简便,主要多了个通过浏览器查看测试结果,个人觉得不是很有使用的必要2. 单元测试应该尽可能简单可维护,嵌套分组等功能太复杂,不利于维护 | |
结论
建议采用testing+testify,goland支持自动生成testing单测模板,加上testify丰富的断言够用了
mock框架
golang中常用的stub/mock框架
GoStub | Gomonkey | Gomock |
---|---|---|
轻量级打桩框架 | 运行时重写可执行文件,类似热补丁 | 官方提供的mock框架,功能强大 |
支持为全局变量,函数打桩 | 性能强大,使用方便 | mockgen 工具可自动生成mock代码;支持mock所有接口类型 |
需要改造原函数,使用不方便;性能不强 | 支持对变量,函数,方法打桩,支持打桩序列 | 可以配置调用次数,调用顺序,根据入参动态返回结果等 |
不是并发安全的;使用可能根据版本不同需要有些额外配置工作 | 只支持接口级别mock,不能mock普通函数 |
结论
建议采用Gomonkey. GoStub很多功能不支持,GoMock每次编写完需要重新generate生成代码,不太方便
其他特定领域mock工具
框架名 | 说明 | |
---|---|---|
GoSqlMock | sqlmock包,用于单测中mock db操作 | |
miniredis | 纯go实现的用于单元测试的redis server。它是一个简单易用的、基于内存的redis替代品,它具有真正的TCP接口。当我们为一些包含 Redis 操作的代码编写单元测试时可以使用它来 mock Redis 操作 | |
Httptest | Golang官方自带,生成一个模拟的http server.主要使用的单测场景是:已经约定了接口,但是服务端还没实现 |
其他
goland中没有类似TestMe的Go单元测试插件,可以考虑实现一个
mock工具GoMock使用
GoMock
gomock 是官方提供的 mock 框架,同时还提供了 mockgen 工具用来辅助生成测试代码。
go get -u github.com/golang/mock/gomock go get -u github.com/golang/mock/mockgen
简单的使用方法:
// db.gotype DB interface {Get(key string) (int, error)}func GetFromDB(db DB, key string) int {if value, err := db.Get(key); err == nil {return value}return -1}复制代码
有一个DB接口,使用mockgen产生一个mock对象
mockgen -source=db.go -destination=db_mock.go -package=main
下面是自动生成的代码
// Code generated by MockGen. DO NOT EDIT.// Source: db.go// Package mian is a generated GoMock package.package mianimport (reflect "reflect"gomock "github.com/golang/mock/gomock")// MockDB is a mock of DB interface.type MockDB struct {ctrl *gomock.Controllerrecorder *MockDBMockRecorder}// MockDBMockRecorder is the mock recorder for MockDB.type MockDBMockRecorder struct {mock *MockDB}// NewMockDB creates a new mock instance.func NewMockDB(ctrl *gomock.Controller) *MockDB {mock := &MockDB{ctrl: ctrl}mock.recorder = &MockDBMockRecorder{mock}return mock}// EXPECT returns an object that allows the caller to indicate expected use.func (m *MockDB) EXPECT() *MockDBMockRecorder {return m.recorder}// Get mocks base method.func (m *MockDB) Get(key string) (int, error) {m.ctrl.T.Helper()ret := m.ctrl.Call(m, "Get", key)ret0, _ := ret[0].(int)ret1, _ := ret[1].(error)return ret0, ret1}// Get indicates an expected call of Get.func (mr *MockDBMockRecorder) Get(key interface{}) *gomock.Call {mr.mock.ctrl.T.Helper()return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDB)(nil).Get), key)}复制代码
在测试的使用mock对象
func TestGetFromDB(t *testing.T) {ctrl := gomock.NewController(t)defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用m := NewMockDB(ctrl)m.EXPECT().Get(gomock.Eq("Tom")).Return(100, errors.New("not exist")) //设置期望返回结果,可以设置可调用次数times/AnyTimesif v := GetFromDB(m, "Tom"); v != -1 {t.Fatal("expected -1, but got", v)}}复制代码
goMock支持对特定输入打桩和对任意输入打桩(gomock.any()),可根据具体情况使用;
实际项目中,可以用gomock来mock dao层和rpc层代码,隔离外部依赖
相关文章:

Go单元测试及框架使用
Go自带测试框架 单元测试 建议Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。函数名必须以 Test 开头,后面一般跟待测试的函数名参数为 t *testing.T 简单测试用例定义如下: func TestXXXX(t *testing.T) {// ...}…...
TreeMap类型实体类数据进行排序
实体类Student类代码如下所示: package com.test.Test11;public class Student implements Comparable<Student>{private int age;private String name;private Double height;public int getAge() {return age;}public void setAge(int age) {this.age age…...

HOOPS助力AVEVA数字化转型:支持多种3D模型格式转换!
行业: 电力和公用事业、化工、造船、能源、采矿业 挑战: 创建大规模复杂资产的客户需要汇集多种类型的数据,以支持初始设计和创建强大的数字双胞胎;现有版本的产品只支持半打CAD格式;有限的内部开发资源限制了增加对新…...

(转载)基于遗传模拟退火的聚类算法(matlab实现)
1 理论基础 1.1 模糊聚类分析 模糊聚类是目前知识发现以及模式识别等诸多领域中的重要研究分支之一。随着研究范围的拓展,不管是科学研究还是实际应用,都对聚类的结果从多方面提出了更高的要求。模糊C-均值聚类(FCM)是目前比较流行的一种聚类方法。该…...

【C++】struct 和 class 的区别
欢迎来到博主 Apeiron 的博客,祝您旅程愉快。时止则止,时行则行。动静不失其时,其道光明。 目录 1、缘起 2、示例代码 3、总结 1、缘起 在 C 中,struct 和 class 唯一的区别就在于 默认的访问权限不同。区别如下: …...

活动笔记丨物业行业人效提升与灵活用工新路径
近日,盖雅工场成功举办物业行业人效提升专场交流,来自广深地区央企和民营的领先物业企业和现场服务业的多位代表齐聚深圳招商积余大厦,共同研讨行业人效提升的挑战和实践。 本次闭门交流会聚焦于人效提升,讨论话题包括各自企业在人…...

学习笔记:吴恩达ChatGPT提示工程
以下为个人笔记,原课程网址Short Courses | Learn Generative AI from DeepLearning.AI 01 Introduction 1.1 基础LLM 输入 从前有一只独角兽,输出 它和其他独角兽朋友一起住在森林里输入 法国的首都在哪?输出 法国的首都在哪…...

POI in Action
POI 组件依赖 按需引入对应依赖 (给出官方的指引) 组件作用Maven依赖POIFSOLE2 FilesystempoiHPSFOLE2 Property SetspoiHSSFExcel XLSpoiHSLFPowerPoint PPTpoi-scratchpadHWPFWord DOCpoi-scratchpadHDGFVisio VSDpoi-scratchpadHPBFPublisher PUBpoi-scratchpadHSMFOutloo…...

苹果Vision Pro将引爆人机交互的重大变革
2023年6月6日,苹果发布了大家期待已久的Vision Pro,Vision Pro是一款专业级MR设备,融合了虚拟现实(VR)和增强现实(AR)技术,可以让用户完全沉浸在高分辨率显示内容中。允许用户以一种全新的方式在其周围的空间中查看APP。用户可以用…...

MMDetection学习记录(二)之配置文件
文件结构 config文件 在 config_base_ 文件夹下有 4 个基本组件类型,分别是:数据集(dataset),模型(model),训练策略(schedule)和运行时的默认设置(default runtime)。 命名风格 {model}_[model setting]_{backbone}_{neck}_[no…...

Python数据分析:NumPy、Pandas和Matplotlib的使用和实践
在现代数据分析领域中,Python已成为最受欢迎的编程语言之一。Python通过庞大的社区和出色的库支持,成为了数据科学家和分析师的首选语言。在Python的库中,NumPy、Pandas和Matplotlib是三个最为重要的库,它们分别用于处理数值数组、…...
实习生面试问题及回答记录
文章目录 文章简介技术类1、DFS和BFS算法的区别是什么?2、解释一下什么是快速排序?3、 如果让你写一个排序算法?你会怎么写?(大概说出代码的思路)4、解释一下二分查找的具体逻辑?5、在代码的数据…...

设计模式(十):结构型之外观模式
设计模式系列文章 设计模式(一):创建型之单例模式 设计模式(二、三):创建型之工厂方法和抽象工厂模式 设计模式(四):创建型之原型模式 设计模式(五):创建型之建造者模式 设计模式(六):结构型之代理模式 设计模式…...

买法拍房需要注意什么
法拍房,由于其价格亲民、房屋信息透明度高、竞拍过程公平公正而受到越来越多的人开始关注。但是其中又有着许多的风险及相关的注意事项。那么,如何做到成功“捡漏”,买法拍房需要注意什么呢? 买法拍房需要注意什么 1、隐藏的各种收费 税费&a…...
linux命令输出结果但不显示在屏幕上的通用办法
linux命令输出结果但不显示在屏幕上的通用办法 这个针对于我这种小白马大哈很简单的一个命令,记给自己备用 举个例子:unzip命令不输出结果 unzip xx.zip > /dev/null 2>&1 unzip xx.zip > /dev/null 前半部分是将标准输出重定向到空设备&a…...
【Linux系统进阶详解】Linux字符权限rwx-权限组合原理,对应类型ugo,user,group,other,+-=详解及权限管理实战
在Linux系统中,每个文件和目录都有三种权限:读权限(r)、写权限(w)和执行权限(x)。这些权限可以被分配给三个不同的用户组:用户(user)、组(group)和其他人(other)。此外,权限可以使用“+”、“-”和“=”符号进行修改。 权限组合原理 Linux系统中的权限由字母…...

凡人修C传——专栏从凡人到成仙系列目录
这里先感谢博主THUNDER王给我提出来的一个创作建议,让我有了创作的灵感来创建这一篇博客以及凡人修C传这一个系列的文章。 本文最主要的目的就是给大家一个凡人修C传的一个目录,让大家更加容易学到自己想学的地方。 📝【个人主页】࿱…...
隐藏python代码,售卖并保护源代码
我写了一个基于pytorch框架的特殊卷积,他的使用方式和其他的卷积一样,但是我想把它卖出去,希望隐藏特殊卷积的代码 1、如果您希望隐藏特殊卷积的代码并将其作为一个可售卖的产品,可以考虑以下几种方法来保护您的代码:…...

Material—— VAT(Houdini To UE)
目录 一,介绍 二,柔体 二,刚体 一,介绍 VAT是将动画数据存储在纹理中,通过GPU运算来实现动画的技术;VAT纹理包含每个顶点在不同帧的位置信息,而每个像素代表一个顶点在某个时间点的位置&…...
视频后期剪辑
文章目录 后期剪辑软件三方插件提供动画制作软件 后期剪辑软件 视频剪辑后期处理涉及到多个软件和插件,下面是对其中几个主要软件及其相关插件的扩展介绍,以及为它们提供插件的一些知名第三方公司。 Adobe After Effects: Adobe After Effec…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
【Ftrace 专栏】Ftrace 参考博文
ftrace、perf、bcc、bpftrace、ply、simple_perf的使用Ftrace 基本用法Linux 利用 ftrace 分析内核调用如何利用ftrace精确跟踪特定进程调度信息使用 ftrace 进行追踪延迟Linux-培训笔记-ftracehttps://www.kernel.org/doc/html/v4.18/trace/events.htmlhttps://blog.csdn.net/…...