Go 语言中的单元测试

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文介绍了Go语言中单元测试的核心方法与实践技巧,涵盖测试文件与函数命名规范、使用`go test`命令执行测试、表格驱动测试优化多场景验证,以及性能测试与耗时测试管理,帮助开发者提升代码质量与项目稳定性。

1、如何编写单元测试

在任何生产级别的项目开发中,单元测试都扮演着至关重要的角色。尽管许多初创项目在早期可能忽略了它,但随着项目逐渐成熟并成为核心业务,为其编写健壮的单元测试是保障代码质量和项目稳定性的必然选择。本文将带您快速掌握 Go 语言中单元测试的基本方法和核心概念。

1、核心命令:go test

Go 语言的测试工具链非常简洁,其核心是 go test 命令。这是一个依据特定约定来组织和驱动测试代码的程序。当你在一个包目录中执行 go test 时,它会自动寻找并执行所有符合测试规范的用例。

2、测试文件的约定

go test 命令的运行依赖于一套简单的文件和函数命名约定:

  1. 文件名约定:在包目录中,所有以 _test.go 为后缀的源文件都会被 go test 命令识别为测试文件并执行。
  2. 构建时排除:您无需担心测试文件会增加最终可执行文件的大小。go build 命令在编译和打包时会自动忽略这些 _test.go 文件,确保它们只存在于测试环境中。

3、测试函数的类型

_test.go 文件中,我们主要会编写以下几种类型的测试函数,目前我们主要关注前两种:

  • 功能测试 (Functional Tests):函数名以 Test 开头,例如 TestMyFunction。这是最常见的测试类型,用于验证代码功能的正确性。
  • 性能测试 (Benchmark Tests):函数名以 Benchmark 开头,例如 BenchmarkMyFunction。用于衡量代码的性能和效率。
  • 示例测试 (Example Tests):函数名以 Example 开头,用于提供可执行的示例代码,常用于文档生成。
  • 模糊测试 (Fuzzing Tests):以 Fuzz 开头,是一种自动化的测试技术,用于发现边界条件下的潜在错误。

4、编写第一个单元测试

让我们通过一个具体的例子来演示如何编写和运行一个单元测试。

1. 准备被测试的代码

首先,我们创建一个 add.go 文件,并在其中定义一个简单的加法函数。

image.png

2. 创建测试文件

接下来,在与 add.go 相同的包(目录)下,我们创建测试文件 add_test.go。将测试文件和源文件放在同一包内,可以方便地测试包内未导出的(小写字母开头的)函数和方法。

image.png

3. 编写测试函数

add_test.go 文件中,我们编写一个功能测试函数来验证 Add 函数的正确性。

测试函数的规范:

  • 函数名必须以 Test 开头,后面通常跟上被测试的函数名,如 TestAdd
  • 参数必须是 t *testing.T*testing.T 类型提供了报告测试失败和记录日志等核心功能。

image.png

编写完成后,许多 IDE(如 GoLand)会自动在函数旁显示一个可运行的标记,这表明 IDE 已经识别出这是一个有效的测试用例。

2、管理和跳过耗时测试

当测试用例数量增多时,某些测试(如涉及网络请求或大量计算的测试)可能会运行得非常缓慢。为了在开发过程中获得快速反馈,我们常常希望可以跳过这些耗时的测试。

Go 语言为此提供了 -short 模式。

工作原理

  1. 在运行测试时,可以附加 -short 标志:go test -v -short
  2. 在测试函数内部,可以通过 testing.Short() 函数进行判断。如果 -short 标志被设置,该函数返回 true
  3. 使用 t.Skip() 方法来跳过当前测试。当 t.Skip() 被调用时,该测试函数会立即终止,并被标记为“已跳过”(SKIPPED),而不会被标记为“失败”(FAILED)。

让我们添加一个模拟的耗时测试 TestLongRunningTask

.正常模式

执行 go test -v,所有测试都会运行,包括耗时的测试。

image.png

可以看到,总耗时超过了2秒。 aly.wureenet.com

b. Short 模式

执行 go test -v -short,耗时的测试将被跳过。

image.png

可以看到,TestLongRunningTask 被标记为 SKIP,并且总耗时非常短。通过这种方式,我们可以灵活地控制运行哪些测试用例,从而提升开发效率。

3、表格驱动测试 (Table-Driven Tests aly.loudvip.com)

当我们需要用多组不同的输入和期望输出来测试同一个函数时(例如,测试正常情况、边界情况、异常情况),为每一组数据编写一个独立的 Test 函数会非常繁琐且难以维护。

表格驱动测试模式优雅地解决了这个问题。其核心思想是将所有测试用例定义在一个“表格”(通常是一个结构体切片)中,然后在一个测试函数内遍历这个表格,执行每一个测试用例。

实现步骤

  1. 定义测试用例结构体:创建一个结构体,用于描述一个完整的测试用例,包含输入参数和期望的输出结果。
  2. 创建测试用例表格:声明一个该结构体的切片,并填充所有需要测试的数据。
  3. 遍历表格执行测试:在测试函数中,使用 for 循环遍历切片。对于每个测试用例,执行被测函数并断言结果。
  4. (推荐) 使用 t.Run 创建子测试:在循环中为每个测试用例创建一个子测试。这样做的好处是,所有测试用例都会被执行(即使中途有失败),并且测试结果会清晰地分组展示,便于定位问题。

代码示例

让我们用表格驱动模式来重构 TestAdd

image.png

当我们运行这个测试时,如果出现错误(例如,我们将第三个用例的期望值 expected 错写成 0),会得到非常清晰的报告。

image.png

这个输出明确地告诉我们,只有名为 input:_-9,_8 的子测试失败了,极大地提高了调试效率。

4、性能测试 (Benchmark Tests aly.pumpums.com)

除了功能正确性,代码的性能也是衡量质量的重要维度。对于一些位于核心路径、对性能有高要求的函数,我们需要进行性能测试。Go 语言内置了强大的性能测试框架。

性能测试的规范

  • 函数命名:性能测试函数必须以 Benchmark 开头,例如 BenchmarkAdd
  • 函数签名:参数必须是 b *testing.B*testing.B 类型提供了控制计时器、设置迭代次数等能力。
  • 核心循环:测试的主体逻辑必须放在一个 for 循环内,循环次数由 b.N 决定:for i := 0; i < b.N; i++go test 命令会自动调整 b.N 的值,反复运行代码直到获得稳定可靠的测量结果。

简单示例

image.png

实践:比较字符串拼接性能

字符串拼接是一个非常常见的操作,不同的实现方式性能差异巨大。下面我们通过性能测试来实际比较三种方法的优劣:fmt.Sprintf+ 操作符和 strings.Builder

image.png

通过命令行运行测试 使用 go test 命令并附加 -bench 标志。. 作为参数表示运行当前包下所有的性能测试。

代码语言:sh

AI代码解释

go test -bench=.

运行获取到的结果,具体数值取决于您的机器性能。

根据提供的基准测试结果,以下是各个字符串拼接方法的性能对比分析:

  1. BenchmarkStringSprintf(使用 fmt.Sprintf 拼接)
  • 执行次数:88 次
  • 每次操作耗时:约 13,797,447 ns (13.8 ms)
  • 分析:性能最差,因为 fmt.Sprintf 在每次调用时都需要进行格式解析和内存分配,效率较低。
  1. BenchmarkStringAdd(使用 + 操作符拼接)
  • 执行次数:99 次
  • 每次操作耗时:约 11,765,825 ns (11.8 ms)
  • 分析:比 fmt.Sprintf 快,但由于字符串是不可变类型,每次 + 操作都会创建新字符串并复制内容,性能依然有限。
  1. BenchmarkStringBuilder(使用 strings.Builder 拼接)
  • 执行次数:9418 次
  • 每次操作耗时:约 123,530 ns (0.12 ms)
  • 分析:性能最优。strings.Builder 是专为高效字符串拼接设计的类型,内部使用 []byte 缓冲区,避免了频繁的内存分配和复制。

总结:

  • strings.Builder 明显优于其他两种方式,尤其在大量字符串拼接操作中表现最佳。
  • 避免在循环中使用 fmt.Sprintf+ 进行字符串拼接,除非对性能要求不高。
  • 推荐在性能敏感场景下优先使用 strings.Builder
相关文章
|
11月前
|
算法 Go
Go 语言泛型 — 泛型语法与示例
本文详解 Go 语言泛型语法与使用示例,涵盖泛型函数、类型声明、类型约束及实战应用,适合入门学习与开发实践。
|
12月前
|
供应链 安全 Go
Go Modules 详解 -《Go语言实战指南》
Go Modules 是 Go 语言官方推出的依赖管理工具,自 Go 1.11 起引入,Go 1.16 成为默认方式。它解决了第三方依赖版本控制、项目脱离 GOPATH 限制及多模块管理等问题。本文全面讲解了 Go Modules 的基本原理、初始化方法、常用命令(如 `go mod init`、`go get` 等)、依赖管理(添加/升级/删除)、子模块开发以及常见问题排查,帮助开发者高效使用 Go Modules 进行项目管理。
|
10月前
|
Web App开发 缓存 Rust
|
10月前
|
Web App开发 存储 Ubuntu
Ubuntu 23.04 新特性一览
Ubuntu 23.04 使用 Linux kernel 6.2,并提供 Mesa 23.0 图形驱动程序来支持一些顶级游戏体验。
402 0
|
10月前
|
Unix 物联网 Linux
都什么年代了,你还不懂啥是Linux操作系统
至于华为鸿蒙操作系统是不是独树一帜,这个留给各位阅读本文的网友们来讨论
496 0
|
机器学习/深度学习 人工智能 监控
为什么选择工作流引擎?三大主流引擎优缺点剖析
工作流引擎是一种用于自动化、管理和监控业务流程的软件系统,通过预定义规则和流程模型协调任务流转。其核心功能包括流程建模、任务分配、状态跟踪和异常处理,能提升企业流程效率30%-50%,减少80%以上的人为错误。典型应用场景涵盖审批、生产、服务和决策类流程。主流引擎如Activiti、Flowable和Camunda各有特色,Camunda因高性能和完整工具链成为复杂项目的首选。未来趋势包括低代码集成、AI优化及云原生架构。
为什么选择工作流引擎?三大主流引擎优缺点剖析
|
存储 SQL NoSQL
mybatis-plus小技能: 分表策略(按年分表和按月分表)
业务场景: 日志、交易流水表或者其他数据量大的表,通过日期进行了水平分表,需要通过日期参数,动态的查询数据。 实现思路:利用MybatisPlus的动态表名插件DynamicTableNameInnerInterceptor ,实现Sql执行时,动态的修改表名。
9816 3
mybatis-plus小技能: 分表策略(按年分表和按月分表)
|
Prometheus 监控 Cloud Native
Grafana 最全详解 ( 图文全面总结 )
Grafana是非常重要的微服务部署监控工具,被广泛应用于大型网站架构,本文全面详解。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Grafana 最全详解  ( 图文全面总结 )
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
417 50
|
存储 人工智能 搜索推荐
RAG系统的7个检索指标:信息检索任务准确性评估指南
大型语言模型(LLMs)在生成式AI领域备受关注,但其知识局限性和幻觉问题仍具挑战。检索增强生成(RAG)通过引入外部知识和上下文,有效解决了这些问题,并成为2024年最具影响力的AI技术之一。RAG评估需超越简单的实现方式,建立有效的性能度量标准。本文重点讨论了七个核心检索指标,包括准确率、精确率、召回率、F1分数、平均倒数排名(MRR)、平均精确率均值(MAP)和归一化折损累积增益(nDCG),为评估和优化RAG系统提供了重要依据。这些指标不仅在RAG中发挥作用,还广泛应用于搜索引擎、电子商务、推荐系统等领域。
8582 2
RAG系统的7个检索指标:信息检索任务准确性评估指南