Go test 命令完整指南:从基础到高级用法

1 概述

go test 是 Go 语言内置的测试工具,用于执行测试函数(test function)、基准函数(benchmark function)和示例函数(example function)。它会自动寻找 *_test.go 文件中符合命名规则的函数并执行。

1.1 测试文件命名规范

  • 测试文件必须以 _test.go 结尾
  • 正常编译操作(go build/go install)会忽略这些文件
  • 名称以 _. 开头的文件即使后缀是 _test.go 也会被忽略

1.2 测试函数类型

  • 测试函数 :必须以 TestXXX 开头,接收 *testing.T 参数
  • 基准函数 :必须以 BenchmarkXXX 开头,接收 *testing.B 参数
  • 示例函数 :必须以 ExampleXXX 开头,为文档提供示例

2 基本用法和测试模式

2.1 运行测试的基本命令

bash 复制代码
# 运行当前目录所有测试
go test

# 显示详细输出
go test -v

# 运行所有包(包括子目录)
go test ./...

# 运行特定包测试
go test math

2.2 测试运行模式

go test 有两种运行模式:

  1. 本地目录模式 :在没有包参数时调用(如 go testgo test -v)。此模式下缓存是禁用的。

  2. 包列表模式 :显示指定包参数时调用(如 go test mathgo test ./...)。此模式下会缓存成功的测试结果。

2.3 测试缓存管理

bash 复制代码
# 清理所有缓存,包括测试缓存
go clean -cache

# 只清理测试缓存
go clean -testcache

# 禁用缓存(使用-count=1)
go test -count=1

测试结果在以下情况下会被缓存:相同的测试二进制文件、命令行选项来自可缓存选项集合(-benchtime, -cpu, -list, -parallel, -run, -short, -v)。要禁用缓存,可使用除可缓存选项之外的任何测试选项。

3 测试函数编写与实践

3.1 测试函数结构

go 复制代码
package mypackage

import "testing"

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

3.2 表驱动测试

对于多个测试用例的场景,推荐使用表驱动测试:

go 复制代码
func TestIsPalindrome(t *testing.T) {
    var tests = []struct {
        input string
        want  bool
    }{
        {"", true},
        {"a", true},
        {"aa", true},
        {"ab", false},
        {"kayak", true},
        {"A man, a plan, a canal: Panama", true},
    }
    
    for _, test := range tests {
        if got := IsPalindrome(test.input); got != test.want {
            t.Errorf("IsPalindrome(%q) = %v", test.input, got)
        }
    }
}

3.3 子测试(Subtests)

使用 t.Run() 创建子测试,可以更好地组织测试用例:

go 复制代码
func TestSumSubTest(t *testing.T) {
    t.Run("1+2", func(t *testing.T) {
        val := Sum(1, 2)
        t.Log("1+2=", val)
    })
    t.Run("2+3", func(t *testing.T) {
        val := Sum(2, 3)
        t.Log("2+3=", val)
    })
}

3.4 并行测试

对于可以并行执行的测试,使用 t.Parallel()

go 复制代码
func TestSumParallel(t *testing.T) {
    t.Parallel()
    // 测试逻辑
}

func TestSumParallel2(t *testing.T) {
    t.Parallel()
    // 测试逻辑
}

使用 -parallel n 指定最大并发数。

3.5 测试初始化与清理

使用 TestMain 作为测试入口点,进行初始化和清理操作:

go 复制代码
func TestMain(m *testing.M) {
    fmt.Printf("测试初始化\n")
    // 初始化代码
    exitCode := m.Run()
    // 清理代码
    fmt.Printf("测试清理\n")
    os.Exit(exitCode)
}

如果没有在 TestMain 中调用 m.Run(),除了 TestMain() 以外的其他 test 都不会执行。

4 基准测试(Benchmark)

4.1 基准测试函数结构

go 复制代码
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(i, i+1)
    }
}

4.2 运行基准测试

bash 复制代码
# 运行所有基准测试
go test -bench=.

# 运行特定基准测试
go test -bench=BenchmarkAdd

# 显示内存分配统计
go test -bench=. -benchmem

# 指定基准测试运行时间
go test -bench=. -benchtime=5s

4.3 基准测试计时器控制

对于需要复杂初始化的基准测试,可以控制计时器:

go 复制代码
func BenchmarkComplexOperation(b *testing.B) {
    // 复杂的初始化操作,不计入基准测试时间
    setup()
    
    b.ResetTimer() // 重置计时器
    for i := 0; i < b.N; i++ {
        complexOperation()
    }
}

还可以使用 b.StopTimer()b.StartTimer() 来暂停和恢复计时。

5 常用参数和标志

5.1 测试选择参数

下表总结了常用的测试选择参数:

参数 描述 示例
-run 运行匹配正则表达式的测试函数 go test -run TestAdd
-bench 运行匹配正则表达式的基准测试 go test -bench BenchmarkAdd
-v 详细输出,显示所有测试日志 go test -v
-count 运行每个测试/基准测试的次数 go test -count=3
-timeout 设置测试超时时间 go test -timeout=30s

5.2 高级控制参数

bash 复制代码
# 随机化测试执行顺序
go test -shuffle=on

# 遇到第一个失败测试就停止
go test -failfast

# 设置并行测试数量
go test -parallel=4

# 列出匹配的测试但不执行
go test -list="TestAdd"

5.3 覆盖率分析

bash 复制代码
# 启用覆盖率分析
go test -cover

# 生成覆盖率文件
go test -coverprofile=cover.out

# 查看详细的覆盖率报告
go tool cover -html=cover.out

# 设置覆盖率模式
go test -covermode=count

覆盖率模式有三种:

  • set(布尔值):语句是否运行
  • count(整数):语句运行了多少次
  • atomic(整数):支持并发场景的计数

6 性能分析

Go test 支持生成多种性能分析文件:

6.1 生成性能分析数据

bash 复制代码
# CPU 性能分析
go test -cpuprofile=cpu.out

# 内存分配分析
go test -memprofile=mem.out

# 阻塞分析
go test -blockprofile=block.out

# 执行跟踪
go test -trace=trace.out

6.2 分析性能数据

生成分析文件后,可以使用 go tool pprof 进行分析:

bash 复制代码
# 分析 CPU 性能
go tool pprof cpu.out

# 分析内存分配
go tool pprof mem.out

# 查看执行跟踪
go tool trace trace.out

7 高级特性与技巧

7.1 测试二进制文件操作

bash 复制代码
# 编译测试二进制文件但不运行
go test -c -o test_binary

# 运行已编译的测试二进制文件
./test_binary -test.v -test.run=TestAdd

# 编译带调试信息的测试二进制文件
go test -c -gcflags="all=-N -l" -o debug_binary

7.2 JSON 格式输出

对于自动化测试,可以输出 JSON 格式的结果:

bash 复制代码
go test -json

7.3 传递参数给测试二进制文件

使用 -args 将参数传递给测试程序:

go 复制代码
func TestArgs(t *testing.T) {
    if !flag.Parsed() {
        flag.Parse()
    }
    argList := flag.Args()
    for _, arg := range argList {
        if arg == "cloud" {
            t.Log("Running in cloud mode.")
        }
    }
}

运行测试时传递参数:

bash 复制代码
go test -run TestArgs -v -args "cloud"

8 实际应用示例

8.1 完整的测试文件示例

go 复制代码
package example

import (
    "flag"
    "fmt"
    "testing"
    "time"
)

var print bool

func init() {
    flag.BoolVar(&print, "p", false, "print test log")
}

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

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(i, i+1)
    }
}

func ExampleAdd() {
    sum := Add(1, 2)
    fmt.Println(sum)
    // Output: 3
}

func TestMain(m *testing.M) {
    flag.Parse()
    fmt.Printf("测试初始化\n")
    exitCode := m.Run()
    fmt.Printf("测试清理\n")
    os.Exit(exitCode)
}

8.2 测试 HTTP 处理程序

go 复制代码
package http_test

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHandler(t *testing.T) {
    req, err := http.NewRequest("GET", "/health", nil)
    if err != nil {
        t.Fatal(err)
    }
    
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(HealthHandler)
    
    handler.ServeHTTP(rr, req)
    
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v", 
            status, http.StatusOK)
    }
    
    expected := `{"status":"healthy"}`
    if rr.Body.String() != expected {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), expected)
    }
}

9 最佳实践

  1. 保持测试独立:每个测试应该独立于其他测试,不依赖执行顺序。

  2. 使用有意义的测试名称:测试名称应该清晰描述被测试的功能。

  3. 测试错误情况:不仅要测试正常情况,还要测试错误和边界情况。

  4. 避免睡眠 :在测试中避免使用 time.Sleep,使用同步机制或测试助手函数。

  5. 并行化长时间测试 :对于长时间运行的测试,使用 t.Parallel() 来加速测试执行。

  6. 定期检查测试覆盖率:使用覆盖率工具确保测试足够全面。

  7. 保持测试快速:测试应该快速执行,以便频繁运行。

通过掌握 go test 的各种特性和技巧,您可以构建健壮、可维护的测试套件,确保代码质量并促进持续集成流程。

相关推荐
n8n4 小时前
Go tool pprof 与 Gin 框架性能分析完整指南
go·gin
余大侠在劈柴10 小时前
go语言学习记录9.23
开发语言·前端·学习·golang·go
心月狐的流火号11 小时前
Go方法接收者语义与嵌入类型方法提升
后端·go
lypzcgf20 小时前
Coze源码分析-资源库-创建数据库-后端源码-安全与错误处理
数据库·安全·go·coze·coze源码分析·ai应用平台·agent平台
n8n1 天前
Go语言在区块链开发中的应用场景详解
go·区块链
小羊在睡觉1 天前
Go语言爬虫:爬虫入门
数据库·后端·爬虫·golang·go
PFinal社区_南丞1 天前
Go-testing-synctest-深度解析与实战指南
后端·go
程序员爱钓鱼2 天前
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
后端·google·go
n8n2 天前
Go 语言错误处理深度技术文档:从基础到高级实践
go