/ Go 语言 Test 测试函数详解 /
一、概述
测试是保证代码质量非常重要的一环。Go 语言内置了测试功能,可以轻松编写单元测试。本文将详细介绍 Go 语言 testing 测试功能中的 Test 测试函数,包括使用方式、测试组织、benchmark 性能测试、示例代码等。
主要内容:
- Test 函数基本用法
- 子测试与 Setup/Teardown
- 测试组织结构
- 测试 cover 率
- 基准测试 benchmark
- 测试主函数
- 测试框架集成
- 并发测试
- 示例 Demo
通过本文可以全面了解 Go 语言 Test 测试函数,并能应用到实际项目中去提高代码质量。
二、Test 函数基本用法
Go 语言中单元测试文件以 _test.go 命名,测试方法名以 Test 开头:
func TestAdd(t *testing.T) { total := Add(1, 2) if total != 3 { t.Errorf("Add(1, 2) failed, got %d", total) } }
测试函数 TestAdd 中,使用 t.Errorf 来报告错误信息。
testing.T 实际上是一个接口,代表一个测试对象,不同的测试框架都可以实现这个接口。我们通过它来记录测试日志。
常用的方法有:
- t.Error/t.Errorf: 记录错误
- t.Fatal/t.Fatalf: 记录错误并退出
- t.Log/t.Logf: 记录日志
- t.Parallel: 并发测试
Go 语言内置的 testing 框架实现了 testing.T 接口。
三、子测试与 Setup/Teardown
我们可以通过 Run 方法执行子测试,还可以通过 Setup/Teardown 执行初始化和清理:
func TestMulti(t *testing.T) { // 初始化代码 t.Setup(func() { // .. }) t.Run("A=1", func(t *testing.T) { t.Log("A=1") }) t.Run("A=2", func(t *testing.T) { t.Log("A=2") }) // 清理代码 t.Teardown(func() { // .. }) }
这样可以非常方便地对测试进行分组。
四、测试组织结构
复杂的测试我们可以通过以下结构组织:
- testMain:全局测试初始化和 ErrorHandler
- TestSuite:测试套件初始化,全局资源
- TestCase:一个测试用例,一组相关的测试
- Test:单个测试函数
- subTest:子测试用例
这种层次清晰的测试组织结构,可以让我们完整体系地编写测试代码。
五、测试覆盖率
测试覆盖率(cover rage)可以度量测试的覆盖面。
启用 coverrage 实际上是通过注入计数器来实现。以示例:
func TestA(t *testing.T) { // 开启coverrage c := cover.NewCounter("testfile") c.Enable() // 测试代码 ... // 查看测试覆盖率 cover.Percent(c) }
执行后可以得到运行的测试覆盖了百分之多少的代码。
这可以帮助我们分析还需要增加哪些测试。
六、基准测试
基准测试 Benchmark 可以测试函数/代码片段的性能。基准测试以 Benchmark 为前缀:
func BenchmarkAdd(b *testing.B) { for i := 0; i < b.N; i++ { Add(1, 2) } }
运行基准测试,可以得到代码执行的时间。这样可以比较不同实现的性能。
我们应该针对关键路径进行基准测试,找到并优化速度慢的代码。
七、测试主函数
每份测试代码需要一个 TestMain 函数作为入口:
func TestMain(m *testing.M) { // 初始化代码 // 执行测试 code := m.Run() // 释放资源 ... os.Exit(code) }
TestMain 会先执行初始化操作,然后使用 m.Run 执行测试代码,最后释放资源并退出程序。/
八、测试框架集成
Go 语言内置 testing 框架非常简单。我们也可以集成其他测试框架。
常见的集成方式是实现 testing.TB 接口。以集成 Ginkgo 为例:
type TestWrapper struct { *testing.T } func (t *TestWrapper) Fail() { t.FailNow() } var RunSpecs = func(t *testing.T) { RegisterFailHandler(Fail) var tr TestReporter RunSpecsWithDefaultAndCustomReporters(t, "SuiteName", []Reporter{tr}) } func TestSuite(t *testing.T) { RunSpecs(t) }
这样我们就可以在 Go 测试中无缝使用 Ginkgo 的 BDD 语法了。
九、并发测试
Go 语言支持并发执行测试来提高效率:
func TestParallel(t *testing.T) { for _, fn := range tests { t.Run(fn.Name(), func(t *testing.T) { t.Parallel() // 并发执行测试函数 fn(t) }) } }
通过 t.Parallel()可以启用并发测试。
需要注意并发访问全局资源的锁保护以避免冲突。
十、完整示例
我们来看一个完整的测试示例:
// 测试对象 type Foo struct {} // 测试函数 func TestFoo(t *testing.T) { // 测试套件初始化 t.SetupTestSuite(func() { // .. }) t.Run("A=1", func(t *testing.T) { t.Parallel() // 并发 // 测试前初始化 t.Setup(func() { // .. }) // 调用测试对象 f := Foo{} // 测试逻辑 ... // 测试后清理 t.Teardown(func() { // .. }) }) // 基准测试 t.Benchmark("BM", func(b *testing.B) { for i := 0; i < b.N; i++ { // .. } }) // 测试套件结束处理 t.TeardownTestSuite(func() { // .. }) } func TestMain(m *testing.M) { // 初始化 os.Exit(m.Run()) }
这样一个完整的测试组织结构,可以让我们的测试代码看起来非常清晰系统。
十一、总结
通过本文的介绍,我们已经可以熟练使用 Go 语言中的测试功能,编写规范的单元测试了。
testing 提供了强大灵活的测试支持,可以编写出高质量可靠的测试代码。这对于提高我们项目的健壮性非常重要。
在后续的 Go 项目中,希望大家积极编写 Unit Test,让代码质量达到更高的层次。