golang t *testing.T、b *testing.B、m *testing.M、pb *testing.PB、f *testing.F 等的区别

作者: adm 分类: go 发布时间: 2025-04-15

t *testing.T、b *testing.B、m *testing.M、pb *testing.PB、f *testing.F 等是 Go testing 包中不同的测试上下文类型,它们各自用于不同的测试场景,拥有不同的方法和行为。
单元测试规范

测试函数必须放在 _test.go 结尾的文件中,方法名必须Test开头,紧接着大写开头的函数名称
普通测试,方法名应该是 TestXxx(t *testing.T),其中 Xxx 可以是任意名称(不能是 Main)

简单来说:它们都是”测试函数的参数”,但类型不同,决定了这个测试函数是做什么类型的测试。

一、核心类型概览

类型	常用别名	用途	函数命名规则	执行命令
*testing.T	t	单元测试、功能测试	TestXxx	go test
*testing.B	b	基准测试、性能测试	BenchmarkXxx	go test -bench
*testing.M	m	测试主控、全局初始化/清理	TestMain	go test
*testing.PB	pb	并行测试控制(在子测试中)	配合 b.RunParallel	go test -bench
*testing.F	f	模糊测试、随机输入生成	FuzzXxx	go test -fuzz

二、详细对比与示例

1. *testing.T – 单元测试(最常用)

用途:验证代码逻辑是否正确

关键方法:

t.Log():输出日志
t.Error():标记失败,继续执行
t.Fatal():标记失败,立即终止
t.Skip():跳过测试
t.Parallel():并行执行

go

func TestAdd(t *testing.T) {
    t.Log("开始测试")           // 普通日志
    if 1+1 != 2 {
        t.Error("加法失败")      // 记录错误但不停止
    }
    if 2+2 != 4 {
        t.Fatal("致命错误")      // 立即停止当前测试
    }
    t.Skip("跳过剩余测试")        // 跳过后续代码
}

2. *testing.B – 基准测试(性能测试)

用途:测量代码执行时间、内存分配

关键特性:

b.N:自动调整的循环次数(框架会找到稳定值)
b.ResetTimer():重置计时器
b.StopTimer() / b.StartTimer():暂停/恢复计时
b.ReportAllocs():报告内存分配统计
b.RunParallel():并行性能测试

go

func BenchmarkAdd(b *testing.B) {
    // 性能测试前的准备工作
    b.ResetTimer()  // 重置计时器,忽略准备时间
    
    // b.N 由测试框架自动决定
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(1, 2)
        }
    })
}

输出示例:

text
BenchmarkAdd-8    1000000000    0.25 ns/op    0 B/op    0 allocs/op
-8:8个CPU核心
1000000000:执行次数(b.N)
0.25 ns/op:每次操作耗时
0 B/op:每次操作内存分配
0 allocs/op:每次操作分配次数

3. *testing.M - 测试主控(全局控制)

用途:在整个测试套件运行前后进行全局初始化和清理

关键方法:

m.Run():运行所有测试,返回退出码

go

func TestMain(m *testing.M) {
    // 全局初始化(连接数据库、加载配置等)
    fmt.Println("所有测试开始前执行")
    
    // 运行所有测试
    code := m.Run()
    
    // 全局清理(关闭连接、删除临时文件等)
    fmt.Println("所有测试结束后执行")
    
    // 退出(必须)
    os.Exit(code)
}

func TestA(t *testing.T) {
    fmt.Println("测试A")
}

func TestB(t *testing.T) {
    fmt.Println("测试B")
}

输出:

text
所有测试开始前执行
测试A
测试B
所有测试结束后执行

4. *testing.PB - 并行测试控制

用途:在基准测试的并行模式下控制迭代

出现场景:仅在 b.RunParallel() 的回调函数中作为参数

关键方法:

pb.Next():返回是否还有下一次迭代

go

func BenchmarkParallelAdd(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {  // 循环直到没有更多工作
            Add(1, 2)
        }
    })
}

5. *testing.F - 模糊测试(Go 1.18+)

用途:通过随机生成的输入来发现边界情况、崩溃和逻辑错误

关键方法:

f.Add():添加种子数据
f.Fuzz():定义模糊测试函数

go

func FuzzAdd(f *testing.F) {
    // 添加种子数据(正常值、边界值、异常值)
    f.Add(1, 2)      // 正数
    f.Add(-1, 1)     // 负数
    f.Add(0, 0)      // 零
    f.Add(1000000, 2000000)  // 大数
    
    // 模糊测试目标
    f.Fuzz(func(t *testing.T, a, b int) {
        result := Add(a, b)
        
        // 验证性质:加法交换律
        if Add(a, b) != Add(b, a) {
            t.Errorf("交换律失败: %d+%d != %d+%d", a, b, b, a)
        }
        
        // 验证性质:加法结合律
        if Add(Add(a, b), c) != Add(a, Add(b, c)) {
            t.Errorf("结合律失败")
        }
    })
}

运行命令:

go test -fuzz=FuzzAdd -fuzztime=30s

三、快速对比表

特性	*testing.T	*testing.B	*testing.M	*testing.PB	*testing.F
主要用途	功能正确性	性能测量	全局控制	并行迭代	随机测试
运行次数	1次	N次(自动调整)	1次	N次(并行)	多次(随机)
是否自动循环	否	是(通过b.N)	否	是(通过pb.Next)	是(自动生成)
可并行执行	是(t.Parallel)	是(RunParallel)	否	是	是(多进程)
常见方法	Log/Error/Fatal	ResetTimer/StopTimer	Run	Next	Add/Fuzz
Go版本要求	1.0+	1.0+	1.0+	1.0+	1.18+

四、实际应用场景

场景1:测试一个HTTP处理函数

func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/api/user", nil)
    w := httptest.NewRecorder()
    handler(w, req)
    if w.Code != http.StatusOK {
        t.Errorf("期望200,得到%d", w.Code)
    }
}

场景2:性能对比两种排序算法

func BenchmarkQuickSort(b *testing.B) {
    data := generateRandomSlice(1000)
    for i := 0; i < b.N; i++ {
        quickSort(data)
    }
}

func BenchmarkBubbleSort(b *testing.B) {
    data := generateRandomSlice(1000)
    for i := 0; i < b.N; i++ {
        bubbleSort(data)
    }
}

场景3:所有测试前连接数据库

var db *sql.DB

func TestMain(m *testing.M) {
    // 初始化
    var err error
    db, err = sql.Open("mysql", "user:pass@/dbname")
    if err != nil {
        fmt.Println("数据库连接失败")
        os.Exit(1)
    }
    
    // 运行测试
    code := m.Run()
    
    // 清理
    db.Close()
    os.Exit(code)
}

场景4:测试字符串解析的边界情况

func FuzzParseInt(f *testing.F) {
    seeds := []string{"0", "123", "-456", "9999999999", "abc"}
    for _, s := range seeds {
        f.Add(s)
    }
    
    f.Fuzz(func(t *testing.T, s string) {
        result, err := strconv.Atoi(s)
        if err == nil {
            // 如果解析成功,结果应该是数字
            _ = result
        }
    })
}

五、关键区别总结

*testing.T:功能测试,验证"做得对不对"
*testing.B:性能测试,测量"做得快不快"
*testing.M:全局控制,在"所有测试前后"做事
*testing.PB:并行控制,配合基准测试的"并发迭代"
*testing.F:模糊测试,让"计算机帮你找Bug"

记忆技巧:

T = Test(测试)
B = Benchmark(基准)
M = Main(主控)
PB = Parallel Benchmark(并行基准)
F = Fuzz(模糊)

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!