加载中...

Golang 单元测试指南

Golang 单元测试指南

Go 语言自带了一个轻量级的测试框架 testinggo test 命令,这使得编写和执行测试变得非常简单和高效。本文将介绍如何在 Go 中编写单元测试、表驱动测试以及基准测试。

一、基础单元测试

在 Go 中,测试文件通常以 _test.go 结尾,并与被测试的代码放在同一个包中。测试函数必须以 Test 开头,并接收一个 *testing.T 类型的参数。

假设我们有一个简单的加法函数 Add

// calc.go
package calc

func Add(a, b int) int {
    return a + b
}

对应的测试文件 calc_test.go 如下:

// calc_test.go
package calc

import "testing"

func TestAdd(t *testing.T) {
    result := Add(1, 2)
    expected := 3
    if result != expected {
        t.Errorf("Add(1, 2) = %d; expected %d", result, expected)
    }
}

运行测试很简单,只需在终端执行:

go test

二、表驱动测试 (Table-Driven Tests)

Go 社区非常推崇“表驱动测试”。这种方式将测试数据(输入和预期输出)定义在一个切片或数组中,然后遍历这个集合执行测试。这样可以轻松添加新的测试用例,且代码结构清晰。

func TestAddTableDriven(t *testing.T) {
    // 定义测试用例结构
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 1, 2, 3},
        {"negative numbers", -1, -2, -3},
        {"mixed numbers", -1, 1, 0},
        {"zero", 0, 0, 0},
    }

    // 遍历测试用例
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

使用 t.Run 可以创建子测试,这样在运行 go test -v 时,可以看到每个子测试的名称和结果。

三、TestMain:测试的 Setup 和 Teardown

有时候我们需要在所有测试运行之前进行一些初始化操作(如连接数据库),或在测试结束后清理资源。这时可以使用 TestMain

func TestMain(m *testing.M) {
    // setup code...
    println("Before all tests")

    // 运行测试
    code := m.Run()

    // teardown code...
    println("After all tests")

    // 退出
    os.Exit(code)
}

注意:如果有 TestMain,测试不会自动运行,必须显式调用 m.Run()

四、基准测试 (Benchmark)

除了单元测试,Go 还内置了基准测试框架,用于测试代码的性能。基准测试函数必须以 Benchmark 开头,接收 *testing.B 参数。

func BenchmarkAdd(b *testing.B) {
    // b.N 会被框架自动调整,直到运行时间足够长
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

运行基准测试:

go test -bench=.

如果想要查看内存分配情况,可以添加 -benchmem 参数:

go test -bench=. -benchmem

五、 并发测试 (Parallel Testing)

如果你的测试用例之间相互独立,可以使用 t.Parallel() 让它们并发运行,从而显著减少测试的总耗时。

func TestParallel(t *testing.T) {
    t.Parallel() // 标记该测试可以与其他测试并发运行
    // ... actual test logic ...
}

六、 模糊测试 (Fuzzing)

从 Go 1.18 开始,Go 原生支持模糊测试。Fuzzing 可以自动生成随机输入来查找代码中的边界情况或 crash。

func FuzzAdd(f *testing.F) {
    // 添加种子语料库 (Seed Corpus)
    f.Add(1, 2)
    
    f.Fuzz(func(t *testing.T, a int, b int) {
        // 运行被测函数
        result := Add(a, b)
        
        // 验证性质(例如:加法满足交换律)
        if result != Add(b, a) {
            t.Errorf("Commutative property failed: %d + %d != %d + %d", a, b, b, a)
        }
    })
}

运行 Fuzz 测试:go test -fuzz=Fuzz

七、 测试辅助函数 (Test Helpers)

当你提取公共的测试逻辑到辅助函数时,如果测试失败,错误日志通常会指向辅助函数内部,而不是调用它的测试用例。使用 t.Helper() 可以标记当前函数为辅助函数,报错时会展示调用者的行号。

func assertEqual(t *testing.T, got, want int) {
    t.Helper() // 标记为辅助函数
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

八、常用测试命令参数

  • go test -v: 显示详细输出,包括每个测试用例的名称。
  • go test -cover: 显示测试覆盖率。
  • go test -run <RegExp>: 只运行匹配正则表达式的测试函数。
  • go test -bench .: 运行当前包下的所有基准测试。
  • go test -benchmem .: 在运行基准测试时显示内存分配统计信息。
  • go test -race: 数据竞争检测
L-Pig
L-Pig
© 2025 by L-Pig 本文基于 CC BY-NC-SA 4.0 许可 CC 协议 必须注明创作者 仅允许将作品用于非商业用途 改编作品必须遵循相同条款进行共享 最后更新:2026/2/4