第 22 章 - Go语言 测试与基准测试
在Go语言中,测试是一个非常重要的部分,它帮助开发者确保代码的正确性、性能以及可维护性。Go语言提供了一套标准的测试工具,这些工具可以帮助开发者编写单元测试、表达式测试(通常也是指单元测试中的断言)、基准测试等。
单元测试 (Unit Testing)
单元测试主要用于验证程序中最小可测试单元的正确性,如一个函数或方法。Go语言使用testing
包来支持单元测试。
示例
假设我们有一个简单的加法函数Add
,我们想要为这个函数编写单元测试。
源代码: add.go
package mainimport "fmt"// Add two integers and return the result.
func Add(a int, b int) int {return a + b
}func main() {fmt.Println(Add(1, 2))
}
测试代码: add_test.go
package mainimport ("testing"
)// TestAdd checks if the Add function works as expected.
func TestAdd(t *testing.T) {tests := []struct {a, b, want int}{{1, 2, 3},{0, 0, 0},{-1, 1, 0},{5, -3, 2},}for _, tt := range tests {testname := fmt.Sprintf("%d+%d", tt.a, tt.b)t.Run(testname, func(t *testing.T) {ans := Add(tt.a, tt.b)if ans != tt.want {t.Errorf("got %d, want %d", ans, tt.want)}})}
}
在这个例子中,我们定义了一个测试表tests
,其中包含了一些输入值和预期结果。对于每个测试用例,我们调用Add
函数并检查返回的结果是否符合预期。如果不符合,我们使用t.Errorf
报告错误。
表达式测试 (Expression Testing)
表达式测试通常是指在单元测试中使用断言来简化测试逻辑。虽然Go语言的标准库没有直接提供断言功能,但是可以通过第三方库如testify/assert
来实现更简洁的测试代码。
使用 testify 的测试代码: add_test.go
package mainimport ("testing""github.com/stretchr/testify/assert"
)func TestAddWithAssert(t *testing.T) {assert.Equal(t, 3, Add(1, 2), "1+2 should equal 3")assert.Equal(t, 0, Add(0, 0), "0+0 should equal 0")assert.Equal(t, 0, Add(-1, 1), "-1+1 should equal 0")assert.Equal(t, 2, Add(5, -3), "5-3 should equal 2")
}
这里使用了testify/assert
库的assert.Equal
函数来比较期望值和实际值,并且提供了失败时的错误信息。
基准测试 (Benchmark Testing)
基准测试用于测量代码的性能。Go语言的testing
包也支持基准测试。
基准测试代码: add_benchmark_test.go
package mainimport "testing"// BenchmarkAdd measures the performance of the Add function.
func BenchmarkAdd(b *testing.B) {for i := 0; i < b.N; i++ {Add(1, 2)}
}
在这个基准测试中,我们通过循环调用Add
函数来测量其执行速度。b.N
是测试框架自动设置的一个变量,表示应该执行的次数。运行基准测试可以使用命令go test -bench=.
。
以上就是Go语言中单元测试、表达式测试和基准测试的基本介绍和示例。希望这些内容能帮助您更好地理解和使用Go语言的测试功能。
接下来,我们将进一步深入探讨一些高级特性和最佳实践,包括子测试、表格驱动测试、并发测试、测试覆盖率以及如何组织测试代码。
子测试 (Subtests)
子测试允许在一个测试函数内部组织多个相关的测试用例。这有助于提高测试的可读性和组织性,特别是在处理复杂的测试场景时。
改进后的测试代码: add_test.go
package mainimport ("testing"
)func TestAddSubtests(t *testing.T) {cases := []struct {name stringa, b, want int}{{"positive", 1, 2, 3},{"zero", 0, 0, 0},{"negative", -1, 1, 0},{"mixed", 5, -3, 2},}for _, c := range cases {t.Run(c.name, func(t *testing.T) {got := Add(c.a, c.b)if got != c.want {t.Errorf("Add(%d, %d) = %d; want %d", c.a, c.b, got, c.want)}})}
}
在这个例子中,我们使用t.Run
来创建子测试,每个子测试都有一个名称,这样在测试失败时可以更容易地定位到具体的问题。
并发测试 (Concurrent Testing)
Go语言的并发模型非常适合编写高效的测试代码。使用testing.T
的Parallel
方法可以在多个CPU核心上并行运行测试,从而加快测试速度。
并发测试代码: add_parallel_test.go
package mainimport ("testing"
)func TestAddParallel(t *testing.T) {cases := []struct {a, b, want int}{{1, 2, 3},{0, 0, 0},{-1, 1, 0},{5, -3, 2},}for _, c := range cases {c := c // 必须创建一个新的副本t.Run(fmt.Sprintf("%d+%d", c.a, c.b), func(t *testing.T) {t.Parallel()got := Add(c.a, c.b)if got != c.want {t.Errorf("Add(%d, %d) = %d; want %d", c.a, c.b, got, c.want)}})}
}
在这个例子中,我们使用t.Parallel()
来标记每个子测试可以并行运行。需要注意的是,每个子测试必须是线程安全的,因此我们在循环中创建了新的副本c
。
测试覆盖率 (Test Coverage)
测试覆盖率是一种度量测试代码质量的方法,它显示了哪些代码路径已经被测试覆盖。Go语言提供了内置的工具来生成测试覆盖率报告。
生成测试覆盖率报告:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
第一行命令会生成一个覆盖率文件coverage.out
,第二行命令会将这个文件转换为HTML格式,以便于查看。
组织测试代码 (Organizing Test Code)
良好的测试代码组织结构可以使项目更加清晰和易于维护。通常,测试文件应与被测试的源文件放在同一个目录下,并以_test.go
结尾。
目录结构示例:
myproject/
├── add.go
├── add_test.go
└── main.go
每个源文件都应该有一个对应的测试文件,这样可以确保每个功能模块都有相应的测试。
总结
通过上述内容,我们可以看到Go语言的测试工具非常强大和灵活,能够满足各种测试需求。无论是简单的单元测试、复杂的并发测试还是详细的基准测试,Go语言都能提供有效的支持。希望这些内容对您有所帮助,如果您有任何问题或需要进一步的指导,请随时告诉我!
接下来我们将进一步探讨一些更高级的测试技巧和最佳实践,包括模拟(Mocking)、依赖注入、测试辅助函数、以及如何处理外部依赖。
模拟 (Mocking)
在测试中,模拟(Mocking)是一种常用的技术,用于隔离被测试代码与其他组件的依赖关系。通过模拟,我们可以控制外部依赖的行为,从而更好地测试我们的代码。
使用 gomock
进行模拟
gomock
是一个流行的 Go 语言模拟库,可以帮助我们生成和管理模拟对象。
安装 gomock
和 mockgen
:
go get github.com/golang/mock/mockgen
定义接口:
假设我们有一个 Calculator
接口,我们需要测试一个使用该接口的函数。
calculator.go:
package maintype Calculator interface {Add(a, b int) intSubtract(a, b int) int
}
生成模拟对象:
使用 mockgen
生成模拟对象。
mockgen -source=calculator.go -package=main > calculator_mock.go
生成的模拟对象:
calculator_mock.go:
package mainimport ("reflect""testing"
)// MockCalculator is a mock of Calculator interface
type MockCalculator struct {mock.Mock
}// Add provides a mock function with given fields: a, b
func (_m *MockCalculator) Add(a int, b int) int {ret := _m.Called(a, b)var r0 intif rf, ok := ret.Get(0).(func(int, int) int); ok {r0 = rf(a, b)} else {r0 = ret.Get(0).(int)}return r0
}// Subtract provides a mock function with given fields: a, b
func (_m *MockCalculator) Subtract(a int, b int) int {ret := _m.Called(a, b)var r0 intif rf, ok := ret.Get(0).(func(int, int) int); ok {r0 = rf(a, b)} else {r0 = ret.Get(0).(int)}return r0
}
测试代码:
calculator_test.go:
package mainimport ("testing""github.com/golang/mock/gomock"
)func TestUseCalculator(t *testing.T) {ctrl := gomock.NewController(t)defer ctrl.Finish()mockCalc := NewMockCalculator(ctrl)mockCalc.EXPECT().Add(1, 2).Return(3)mockCalc.EXPECT().Subtract(5, 3).Return(2)result := UseCalculator(mockCalc)if result != 5 {t.Errorf("UseCalculator() = %d; want 5", result)}
}func UseCalculator(calc Calculator) int {sum := calc.Add(1, 2)diff := calc.Subtract(5, 3)return sum + diff
}
在这个例子中,我们使用 gomock
生成了 Calculator
接口的模拟对象,并在测试中设置了期望的行为。然后,我们调用 UseCalculator
函数并验证其返回值。
依赖注入 (Dependency Injection)
依赖注入是一种设计模式,通过它可以在运行时将依赖项传递给对象,而不是在对象内部硬编码这些依赖项。这使得代码更灵活、更易于测试。
示例:
假设我们有一个 Service
类,它依赖于 Calculator
接口。
service.go:
package maintype Service struct {calc Calculator
}func NewService(calc Calculator) *Service {return &Service{calc: calc}
}func (s *Service) Compute(a, b int) int {sum := s.calc.Add(a, b)diff := s.calc.Subtract(a, b)return sum + diff
}
测试代码:
service_test.go:
package mainimport ("testing""github.com/golang/mock/gomock"
)func TestServiceCompute(t *testing.T) {ctrl := gomock.NewController(t)defer ctrl.Finish()mockCalc := NewMockCalculator(ctrl)mockCalc.EXPECT().Add(1, 2).Return(3)mockCalc.EXPECT().Subtract(1, 2).Return(-1)service := NewService(mockCalc)result := service.Compute(1, 2)if result != 2 {t.Errorf("Service.Compute() = %d; want 2", result)}
}
在这个例子中,我们通过构造函数将 Calculator
接口的实现传递给 Service
对象,从而实现了依赖注入。
测试辅助函数 (Helper Functions)
测试辅助函数可以帮助减少重复代码,使测试代码更加简洁和可维护。
示例:
假设我们有一个通用的辅助函数来验证两个整数是否相等。
test_helpers.go:
package mainimport ("testing"
)func AssertEqual(t *testing.T, got, want int, msg string) {if got != want {t.Errorf("%s: got %d, want %d", msg, got, want)}
}
使用辅助函数的测试代码:
add_test.go:
package mainimport ("testing"
)func TestAddWithHelper(t *testing.T) {cases := []struct {a, b, want int}{{1, 2, 3},{0, 0, 0},{-1, 1, 0},{5, -3, 2},}for _, c := range cases {got := Add(c.a, c.b)AssertEqual(t, got, c.want, fmt.Sprintf("Add(%d, %d)", c.a, c.b))}
}
在这个例子中,我们定义了一个 AssertEqual
辅助函数,并在测试代码中使用它来简化断言逻辑。
处理外部依赖 (Handling External Dependencies)
在测试中处理外部依赖(如数据库、网络服务等)是一个常见的挑战。通常的做法是使用内存数据库、模拟服务器或配置文件来模拟外部依赖。
示例:
假设我们有一个函数需要访问数据库。
database.go:
package mainimport ("database/sql""fmt"
)type DB interface {QueryRow(query string, args ...interface{}) *sql.Row
}func FetchUser(db DB, id int) (string, error) {var name stringerr := db.QueryRow("SELECT name FROM users WHERE id = ?", id).Scan(&name)if err != nil {return "", err}return name, nil
}
测试代码:
database_test.go:
package mainimport ("testing""github.com/DATA-DOG/go-sqlmock"
)func TestFetchUser(t *testing.T) {db, mock, err := sqlmock.New()if err != nil {t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)}defer db.Close()rows := sqlmock.NewRows([]string{"name"}).AddRow("Alice")mock.ExpectQuery("SELECT name FROM users WHERE id = ?").WithArgs(1).WillReturnRows(rows)name, err := FetchUser(db, 1)if err != nil {t.Errorf("unexpected error: %v", err)}if name != "Alice" {t.Errorf("expected Alice, got %s", name)}if err := mock.ExpectationsWereMet(); err != nil {t.Errorf("there were unfulfilled expectations: %s", err)}
}
在这个例子中,我们使用 go-sqlmock
库来模拟数据库连接和查询,从而避免了对实际数据库的依赖。
总结
通过上述内容,我们可以看到 Go 语言提供了丰富的工具和库来支持各种测试需求。从简单的单元测试到复杂的并发测试和外部依赖处理,Go 语言都提供了强大的支持。希望这些内容对您有所帮助,如果您有任何问题或需要进一步的指导,请随时告诉我!
相关文章:

第 22 章 - Go语言 测试与基准测试
在Go语言中,测试是一个非常重要的部分,它帮助开发者确保代码的正确性、性能以及可维护性。Go语言提供了一套标准的测试工具,这些工具可以帮助开发者编写单元测试、表达式测试(通常也是指单元测试中的断言)、基准测试等…...

VB.Net笔记-更新ing
目录 1.1 设置默认VS的开发环境为VB.NET(2024/11/18) 1.2 新建一个“Hello,world”的窗体(2024/11/18) 1.3 计算圆面积的小程序(2024/11/18) 显示/隐式 声明 (2024/11/18&…...

centos 服务器 docker 使用代理
宿主机使用代理 在宿主机的全局配置文件中添加代理信息 vim /etc/profile export http_proxyhttp://127.0.0.1:7897 export https_proxyhttp://127.0.0.1:7897 export no_proxy"localhost,127.0.0.1,::1,172.171.0.0" docker 命令使用代理 例如我想在使用使用 do…...

python语言基础
1. 基础语法 Q: Python 中的变量与数据类型有哪些? A: Python 支持多种数据类型,包括数字(整数 int、浮点数 float、复数 complex)、字符串 str、列表 list、元组 tuple、字典 dict 和集合 set。每种数据类型都有其特定的用途和…...

Python中的Apriori库详解
文章目录 Python中的Apriori库详解一、引言二、Apriori算法原理与Python实现1、Apriori算法原理2、Python实现1.1、数据准备1.2、转换数据1.3、计算频繁项集1.4、提取关联规则 三、案例分析1、导入必要的库2、准备数据集3、数据预处理4、应用Apriori算法5、生成关联规则6、打印…...

MongoDB比较查询操作符中英对照表及实例详解
mongodb比较查询操作符中英表格一览表 NameDescription功能$eqMatches values that are equal to a specified value.匹配值等于指定值。$gtMatches values that are greater than a specified value.匹配值大于指定值。$gteMatches values that are greater than or equal to…...

掌上单片机实验室 – RT-Thread + ROS2 初探(25)
在初步尝试RT-Thread之后,一直在琢磨如何进一步感受它的优点,因为前面只是用了它的内核,感觉和FreeRTOS、uCOS等RTOS差别不大,至于它们性能、可靠性上的差异,在这种学习性的程序中,很难有所察觉。 RT-Threa…...

Kotlin中的?.和!!主要区别
目录 1、?.和!!介绍 2、使用场景和最佳实践 3、代码示例和解释 1、?.和!!介绍 Kotlin中的?.和!!主要区别在于它们对空指针的处理方式。 ?.(安全调用操作符):当变量可能为null时,使用?.可以安全地调用其方法或属性…...

iframe嵌入踩坑记录
iframe嵌入父子页面token问题 背景介绍 最近在做在平台A中嵌入平台B某个页面的需求,我负责的是平台B这边,使这个页面被嵌入后能正常使用。两个平台都实现了单点登录。 其实这是第二次做这个功能了,原本以为会很顺利,但没想到折腾…...

面试小札:Java的类加载过程和类加载机制。
Java类加载过程 加载(Loading) 这是类加载过程的第一个阶段。在这个阶段,Java虚拟机(JVM)主要完成三件事: 通过类的全限定名来获取定义此类的二进制字节流。这可以从多种来源获取,如本地文件系…...

Spring 上下文对象
1. Spring 上下文对象概述 Spring 上下文对象(ApplicationContext)是 Spring 框架的核心接口之一,它扩展了 BeanFactory 接口,提供了更多企业级应用所需的功能。ApplicationContext 不仅可以管理 Bean 的生命周期和配置࿰…...

Wireshark抓取HTTPS流量技巧
一、工具准备 首先安装wireshark工具,官方链接:Wireshark Go Deep 二、环境变量配置 TLS 加密的核心是会话密钥。这些密钥由客户端和服务器协商生成,用于对通信流量进行对称加密。如果能通过 SSL/TLS 日志文件(例如包含密钥的…...

测试人员--如何区分前端BUG和后端BUG
在软件测试中,发现一个BUG并不算难,但准确定位它的来源却常常让测试人员头疼。是前端页面的问题?还是后台服务的异常?如果搞错了方向,开发人员之间的沟通效率会大大降低,甚至导致问题久拖不决。 那么&#…...

【Vue】指令扩充(指令修饰符、样式绑定)
目录 指令修饰符 按键修饰符 事件修饰符 双向绑定指令修饰符 输入框 表单域 下拉框 单选按钮 复选框 样式绑定 分类 绑定class 绑定style tab页切换示例 指令修饰符 作用 借助指令修饰符,可以让指令的功能更强大 分类 按键修饰符:用来…...
Ubuntu20.04 Rk3588 交叉编译ffmpeg7.0
firefly 公司出的rk3588的设备,其中已经安装了gcc 交叉编译工具,系统版本是Ubuntu20.04。 使用Ubuntu20.04 交叉编译ffmpeg_ubuntu下配置ffmpeg交叉编译器为arm-linux-gnueabihf-gcc-CSDN博客文章浏览阅读541次。ubuntu20.04 交叉编译ffmpeg_ubuntu下配…...

HTML常用表格与标签
一、table表格标签: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <!--有大小为1的边框--> <table border"1">…...

网络安全与加密
1.Base64简单说明描述:Base64可以成为密码学的基石,非常重要。特点:可以将任意的二进制数据进行Base64编码结果:所有的数据都能被编码为并只用65个字符就能表示的文本文件。65字符:A~Z a~z 0~9 / 对文件进行base64编码…...

MySQL数据库-索引的介绍和使用
目录 MySQL数据库-索引1.索引介绍2.索引分类3.创建索引3.1 唯一索引3.2 普通索引3.3 组合索引3.4 全文索引 4.索引使用5.查看索引6.删除索引7.索引总结7.1 优点7.2 缺点7.3 索引使用注意事项 MySQL数据库-索引 数据库是用来存储数据,在互联网应用中,数据…...

【图像去噪】论文精读:Pre-Trained Image Processing Transformer(IPT)
请先看【专栏介绍文章】:【图像去噪(Image Denoising)】关于【图像去噪】专栏的相关说明,包含适配人群、专栏简介、专栏亮点、阅读方法、定价理由、品质承诺、关于更新、去噪概述、文章目录、资料汇总、问题汇总(更新中) 文章目录 前言Abstract1. Introduction2. Related…...

Java SE 与 Java EE:基础与进阶的探索之旅
在编程世界中,Java语言以其跨平台、面向对象、丰富的类库等特点,成为了众多开发者和企业的首选编程语言。而Java SE与Java EE,作为Java平台的两个重要组成部分,各自承载着不同的使命,同时又紧密相连,共同构…...

ssm旅游推荐系统的设计与开发
摘 要 旅游推荐系统是一个综合性的在线旅游推荐平台,旨在为用户提供便捷的旅游规划和预定服务。通过该系统,用户能够浏览各类景点信息并进行分类查找,同时获取详尽的景点介绍和相关照片,以辅助做出旅行决策。系统提供在线门票订购…...

【人工智能】用Python和NLP工具构建文本摘要模型:使用NLTK和spaCy进行自然语言处理
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 文本摘要是自然语言处理(NLP)中的关键任务之一,广泛应用于新闻、博客、社交媒体和搜索引擎等场景。通过生成简洁而准确的文本摘要,我们可以大大提升信息处理效率。本文将探讨如何使用Python结合NLP工具…...

51c大模型~合集76
我自己的原文哦~ https://blog.51cto.com/whaosoft/12617524 #诺奖得主哈萨比斯新作登Nature,AlphaQubit解码出更可靠量子计算机 谷歌「Alpha」家族又壮大了,这次瞄准了量子计算领域。 今天凌晨,新晋诺贝尔化学奖得主、DeepMind 创始人哈萨…...

资源控制器--laravel进阶篇
laravel的控制器当中有个资源控制器,这个比较好用。 创建资源控制器 php artisan make:controller PhotoController --resource 创建个路由来使用该资源控制器 use App\Http\Controllers\PhotoController; Route::resource(photos, PhotoController::class); 隐式模型绑定不…...

对象:是什么,使用,遍历对象,内置对象
对象使用: 对象访问:(对象每个属性之间用逗号隔开) 补充:也可以通过 对象名[‘属性名’] 对象方法: 方法名:匿名函数 调用方法不需要控制台打印,只要调用就自动输出值 遍历对象: …...

设计模式:4、命令模式(双重委托)
目录 0、定义 1、命令模式包括四种角色 2、命令模式的UML类图 3、代码示例 0、定义 将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 1、命令模式包括四种角色 接…...

DataWorks快速入门
DataWorks基于MaxCompute、Hologres、EMR、AnalyticDB、CDP等大数据引擎,为数据仓库、数据湖、湖仓一体等解决方案提供统一的全链路大数据开发治理平台。本文以DataWorks的部分核心功能为例,指导您使用DataWorks接入数据并进行业务处理、周期调度以及数据…...

EasyExcel并行导出多个excel文件并压缩下载
EasyExcel并行导出多个excel文件并压缩下载 在SpringBoot应用中,采用同步方式导出Excel文件会导致服务器在生成文件期间阻塞,特别是在处理大量数据时,这种效率较低的方法会严重影响性能。为了解决这个问题,可以采用以下改进措施:首先将导出的数据进行拆分,然后利用Compl…...

圣诞节秘诀
🕰️你想在2024年圣诞节脱颖而出吗?利用我们的数据洞察,发现今年最受欢迎的礼物!无论是在亚马逊、速卖通、Shopify还是直销平台上,我们的排行榜都将帮助您找到最畅销和最受欢迎的产品。立即优化您的库存,以…...

亚信安全发布《2024年第三季度网络安全威胁报告》
《亚信安全2024年第三季度网络安全威胁报告》的发布旨在从一个全面的视角解析当前的网络安全威胁环境。此报告通过详尽梳理和总结2024年第三季度的网络攻击威胁,目的是提供一个准确和直观的终端威胁感知。帮助用户更好地识别网络安全风险,并采取有效的防…...