当前位置: 首页 > article >正文

20 | 如何添加单元测试用例

提示:

  • 所有体系课见专栏:Go 项目开发极速入门实战课;
  • 欢迎加入我的训练营:云原生AI实战营,一个助力 Go 开发者在 AI 时代建立技术竞争力的实战营;
  • 本节课最终源码位于 fastgo 项目的 feature/s16 分支;
  • 更详细的课程版本见:Go 项目开发中级实战课:33 | 项目测试:如何开发单元测试用例?

在实际开发中,不仅要开发功能,而且还要确保这些功能稳定可靠,并且拥有一个不错的性能,要确保这些,就要对代码进行测试。测试分为很多种,例如:功能测试、性能测试、集成测试、端到端测试、单元测试等。

对于开发者来说,需要执行的测试种类一般是单元测试和性能测试。除此之外,Go 还提供了其他类型的测试,例如:模糊测试、示例测试。本节课会详细介绍开发者需要重点关注的单元测试和性能测试用例。

Go 标准库 testing 包介绍

Go 语言自带测试框架 testing,可以用于编写单元测试用例和性能测试用例,并通过 go test 命令运行测试用例。

go test 命令在运行测试用例时,以 Go 包为单位进行测试。运行时需要指定包名,例如:go test <包名>。如果未指定包名,测试将默认作用于运行命令时所在的包。go test 执行时会遍历以 _test.go 结尾的源码文件,并运行其中以 TestBenchmarkExampleFuzz 开头的测试函数。这些源码文件需满足以下规则:

  • 文件名要求: 文件名必须以 _test.go 结尾,且建议与被测试的源文件位于同一个包中;
  • 测试用例函数规范: 测试用例函数需以 TestBenchmarkExampleFuzz 开头;
  • 测试执行顺序: 测试用例的执行顺序按照源码中的定义顺序依次进行;
  • 单元测试函数: 函数名称形如 TestXxx(t *testing.T),其中 Xxx 部分为任意字母数字组合,首字母需大写。例如:Testlogger 是错误的函数名,TestLogger 是正确的函数名。参数 testing.T 可以用于记录错误或测试状态;
  • 性能测试函数: 函数名称形如 BenchmarkXxx (b *testing.B),函数以 b.N 作为循环次数,其中 N 值会动态变化;
  • 示例函数: 示例函数名称形如 ExampleXxx(),没有参数,执行后将其输出与注释 // Output: 中声明的结果进行对比。

testing.T 提供了丰富的方法来管理测试过程和结果,常用方法如下:

  • 输出测试信息:t.Logt.Logf 两个方法可以用来输出测试信息;
  • 输出测试失败信息:t.Errort.Errorf 两个方法可以用来输出测试异常或失败时的信息;
  • 记录致命错误:t.Fatalt.Fatalf 两个方法用来记录致命错误,并退出测试;
  • 标记测试失败:t.Fail 方法用来将当前测试标记为失败,但测试不会退出。t.Failed 方法用来检查当前测试是否已标记为失败;
  • 终止测试:t.FailNow 用于标记当前测试失败,并立即终止当前测试函数的执行;
  • 跳过测试:t.Skipt.Skipf 两个方法可用于跳过当前测试函数的执行,并记录一条备注信息。t.Skipped 方法可用于检测当前测试是否已被跳过;
  • 并行执行测试:t.Parallel 可将测试标记为支持并行运行。

性能测试过程中,需要重点注意 BenchmarkXxx 函数,其参数 testing.B 用于设置动态变化的循环次数(b.N 值),例如:

func BenchmarkResourceID_New(b *testing.B) {
    // 性能测试
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        userID := rid.UserID
        _ = userID.New(uint64(i))
    }
}

Go 测试用例编写

在实际项目开发中,最常编写的是单元测试用例,其次是性能测试用例。在某些场景下,还可能需要编写模糊测试用例和示例测试用例。本节会详细介绍如何编写单元测试、性能测试。

单元测试

新建 internal/pkg/rid/rid_test.go 文件,在文件中添加 ResourceID 数据类型 String() 方法的单元测试用例:

func TestResourceID_String(t *testing.T) {
    // 测试 UserID 转换为字符串
    userID := rid.UserID
    assert.Equal(t, "user", userID.String(), "UserID.String() should return 'user'")

    // 测试 PostID 转换为字符串
    postID := rid.PostID
    assert.Equal(t, "post", postID.String(), "PostID.String() should return 'post'")
}

在编写单元测试用例时,经常需要对比期望值和实际值是否一致,可以直接编写代码来对比,例如:

if expected != actual {
    t.Error("actual value did not match the expected value")
}

但更建议使用优秀的断言包来对比。在 Go 生态中,比较常用的断言包是 github.com/stretchr/testify/assert。

assert 包,提供了一组丰富的断言函数,用于简化 Go 语言中的单元测试用例编写。通过 assert 包,开发者可以用更直观的方式验证测试结果,如检查值相等、布尔值匹配、集合包含关系、错误状态等,从而极大地提测试代码的可读性和开发效率。

实现单元测试用例后,可以通过执行 go test 命令运行测试用例。go test 命令支持不同的命令行选项,从而实现多种测试效果。常用的 go test 命令如下。

(1)执行默认的测试用例

在 internal/pkg/rid 目录下执行命令 go test

$ go test .
ok  github.com/onexstack/miniblog/internal/pkg/rid0.008s

(2)查看更详细的执行信息

要查看更详细的执行信息可以执行 go test -v

 $ go test -v .
=== RUN   TestResourceID_String
--- PASS: TestResourceID_String (0.00s)
=== RUN   TestResourceID_New
--- PASS: TestResourceID_New (0.00s)
=== RUN   FuzzResourceID_New
=== RUN   FuzzResourceID_New/seed#0
=== RUN   FuzzResourceID_New/seed#1
--- PASS: FuzzResourceID_New (0.00s)
    --- PASS: FuzzResourceID_New/seed#0 (0.00s)
    --- PASS: FuzzResourceID_New/seed#1 (0.00s)
PASS
ok  	github.com/onexstack/fastgo/internal/pkg/rid	0.007s

(3)执行测试 N 次

如果要执行测试 N 次可以使用 -count N 命令行选项:

$ go test -v -count 2
=== RUN   TestResourceID_String
--- PASS: TestResourceID_String (0.00s)
=== RUN   TestResourceID_New
--- PASS: TestResourceID_New (0.00s)
=== RUN   TestResourceID_String
--- PASS: TestResourceID_String (0.00s)
=== RUN   TestResourceID_New
--- PASS: TestResourceID_New (0.00s)
=== RUN   FuzzResourceID_New
=== RUN   FuzzResourceID_New/seed#0
=== RUN   FuzzResourceID_New/seed#1
--- PASS: FuzzResourceID_New (0.00s)
    --- PASS: FuzzResourceID_New/seed#0 (0.00s)
    --- PASS: FuzzResourceID_New/seed#1 (0.00s)
=== RUN   FuzzResourceID_New
=== RUN   FuzzResourceID_New/seed#0
=== RUN   FuzzResourceID_New/seed#1
--- PASS: FuzzResourceID_New (0.00s)
    --- PASS: FuzzResourceID_New/seed#0 (0.00s)
    --- PASS: FuzzResourceID_New/seed#1 (0.00s)
PASS
ok  	github.com/onexstack/fastgo/internal/pkg/rid	0.007s

通过上述测试输出可知,每个测试用例被执行了 2 次。

(4)只运行指定的单测用例

此外,你还可以通过指定 -run 参数(-run 参数支持正则表达式)只运行指定的单测用例:

$ go test -v -run TestResourceID_String
=== RUN   TestResourceID_String
--- PASS: TestResourceID_String (0.00s)
PASS
ok  	github.com/onexstack/fastgo/internal/pkg/rid	0.007s

性能测试

性能测试也叫基准测试,是 Go 项目开发中,非常核心的测试用例类型。Go 开发者也需要掌握如何编写性能测试用例。

在 internal/pkg/rid/rid_test.go 文件中,新增 BenchmarkResourceID_New 性能测试用例函数,代码如下:

func BenchmarkResourceID_New(b *testing.B) {
    // 性能测试
    b.ResetTimer() 
    for i := 0; i < b.N; i++ {
        userID := rid.UserID
        _ = userID.New(uint64(i))
    }
} 

上述代码定义了一个基准测试函数,用于测量 userID.New 方法的性能表现。函数通过 b.ResetTimer() 重置计时器,确保计时只统计核心测试代码的执行时间,然后在一个循环中根据 b.N 的值多次调用 userID.New(uint64(i)) 方法,以模拟高频调用场景并评估其性能。

性能测试函数的名称必须以 Benchmark 开头,例如 BenchmarkXxxBenchmark_Xxx。默认情况下,go test 不会执行性能测试函数,需通过指定参数 -test.bench 来运行,-test.bench 后需接正则表达式,例如 go test -test.bench=".*" 表示运行所有性能测试函数。在性能测试中,应在循环体中使用 testing.B.N 来多次循环执行测试代码。

在编写性能测试用例时,如果用例需要进行一些耗时的准备工作以测试目标函数,可以在准备工作完成后调用 b.ResetTimer() 方法重置计时器。

实现性能测试用例后,可以执行 go test 命令来运行性能测试用例。在 internal/pkg/rid 目录下,执行 go test -test.bench=".*" 命令来运行性能测试用例:

$ go test -test.bench=".*"
goos: linux
goarch: amd64
pkg: github.com/onexstack/fastgo/internal/pkg/rid
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkResourceID_New-32    	  180157	      6558 ns/op
PASS
ok  	github.com/onexstack/fastgo/internal/pkg/rid	1.262s

上述测试用例执行结果显示,BenchmarkResourceID_New 用例执行了 180157 次,每次执行的平均时间是 6558 纳秒。1.262s 表示测试用例总的执行时间。

在运行性能测试用例时,还可以通过 -benchtime 命令行选项,来指定性能测试用例的运行时间和运行次数,确保性能测试结果更加稳定,Go 会根据指定的运行时间和运行次数动态调整运行次数(b.N),以确保测试运行的总时长接近设定值。二者的指定方式如下:

  • -benchtime=1x:指定运行次数为 1 次(可改为任意次数,例如 -benchtime=10x 表示运行 10 次);
  • -benchtime=5s:指定基准测试运行时间为 5 秒(可改为其他时间,例如 -benchtime=100ms 表示运行 100 毫秒)。

运行以下命令,并分别指定性能测试用例的运行时间为 30s、运行次数为 100000 次:

$ go test -benchtime=30s -test.bench="^BenchmarkResourceID_New$"
goos: linux
goarch: amd64
pkg: github.com/onexstack/fastgo/internal/pkg/rid
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkResourceID_New-32    	 5459558	      6700 ns/op
PASS
ok  	github.com/onexstack/fastgo/internal/pkg/rid	43.255s
$ go test -benchtime=100000x -test.bench="^BenchmarkResourceID_New$"
goos: linux
goarch: amd64
pkg: github.com/onexstack/fastgo/internal/pkg/rid
cpu: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
BenchmarkResourceID_New-32    	  100000	      6507 ns/op
PASS
ok  	github.com/onexstack/fastgo/internal/pkg/rid	0.661s

在实际运行性能测试用例时,通常会指定运行时间而非运行次数。

测试覆盖率分析

在编写单元测试时,应尽量考虑全面,覆盖所有可能的测试用例,但有时仍可能遗漏一些测试用例。Go 提供了 cover 工具用于统计测试覆盖率。测试覆盖率可以通过以下两条命令完成:

  • go test -coverprofile=cover.out:在测试文件目录下运行测试并统计测试覆盖率;
  • go tool cover -func=cover.out:分析覆盖率文件,用于检查哪些函数未被测试,或者哪些函数内部的分支未完全覆盖。cover 工具通过执行代码的行数与总行数的比例来表示覆盖率。

进入 internal/pkg/rid 目录,执行以下命令,来测试单元测试覆盖率:

$ cd internal/pkg/rid
$ go test -coverprofile=cover.out
$ go tool cover -func=cover.out
github.com/onexstack/fastgo/internal/pkg/rid/rid.go:25:		String			100.0%
github.com/onexstack/fastgo/internal/pkg/rid/rid.go:30:		New			100.0%
github.com/onexstack/fastgo/internal/pkg/rid/salt.go:18:	Salt			100.0%
github.com/onexstack/fastgo/internal/pkg/rid/salt.go:29:	ReadMachineID		72.7%
github.com/onexstack/fastgo/internal/pkg/rid/salt.go:50:	readPlatformMachineID	75.0%
total:								(statements)		81.8%

可以看到 github.com/onexstack/fastgo/internal/pkg/rid 包的单元测试覆盖率为 81.8%


http://www.kler.cn/a/582471.html

相关文章:

  • 含k个3的数(信息学奥赛一本通-1090)
  • 深度学习之目标检测/对象检测
  • Scala编程_实现Rational的基本操作
  • rust语言闭包trait类型编译器推导总结
  • Matlab深度学习ResNet、迁移学习多标签图像分类及超分辨率处理Grad CAM可视化分析COCO、手写数字数据集...
  • 大模型在甲状腺肿瘤预测及治疗方案制定中的应用研究
  • 探索DEHP暴露对小鼠心脏发育的影响:AbMole助力揭示线粒体功能障碍的奥秘
  • 每周一篇——PLG(Promtail+Loki+Grafana)轻量日志方案
  • [JAVASE] 注解
  • 大白话 Vue 中的keep - alive组件,它的作用是什么?在什么场景下使用?
  • APK文件结构与逆向工具链深度解析
  • 【BUG分析】微服务无法读取Nacos中的共享配置
  • AI模型的构建过程是怎样的(下)
  • 网络安全事件响应--应急响应(windows)
  • ES搭建详细指南+常见错误解决方法
  • 兴达易控Profinet 转 ModbusTCP跨网段通信模块
  • 解决用拼音录入汉字时导致的应用退出floating point invalid operation at 0x6b873ec3
  • 【后端开发核心技术全景解读:从云原生到分布式架构的深度实践】
  • SQL中查询日期的常见方式+应用场景+效率对比
  • 数据分析之- numpy 02 - 基础操作演示