测试与基准
testing 包、table-driven、httptest、benchmarks。
测试与基准
这一课你将能做到
- 写出表驱动测试(table-driven tests),并用
t.Run组织子测试、用-run精准过滤 - 使用
go test -v / -cover / -race等常用参数定位问题与查看覆盖率 - 编写基准测试(benchmark),读懂 ns/op 与内存分配数据;用 httptest 测 HTTP handler
为什么要学
Go 把测试做进了工具链:不需要 JUnit/pytest 这类三方框架,新建 _test.go 文件、写 TestXxx 函数、执行 go test,覆盖率、基准、竞态检测全部内置。
更重要的是 Go 社区有一套高度统一的测试审美——表驱动测试:把「用例数据」排成表格,循环跑断言。打开任何 Go 标准库源码,测试都是这个写法。学会它 = 获得读懂整个 Go 生态测试代码的通行证。面试中「说说表驱动测试」「怎么测 HTTP」也是 Go 岗位的固定题。
先建立图像
项目结构(测试文件与被测代码同包同目录):
calc/
├── go.mod
├── calc.go ← 被测代码 package calc
└── calc_test.go ← 测试代码 package calc(同名包 + _test.go 后缀)
三种测试函数:
func TestXxx(t *testing.T) {} ← 单元测试:go test
func BenchmarkXxx(b *testing.B) {} ← 性能基准:go test -bench=.
func ExampleXxx() {} ← 示例文档:go test 校验输出注释
用例表格(表驱动的心脏):
┌──────────┬────┬────┬──────┬──────────┐
│ name │ a │ b │ want │ wantErr │
├──────────┼────┼────┼──────┼──────────┤
│ 正常除法 │ 10 │ 2 │ 5 │ nil │
│ 除零 │ 1 │ 0 │ 0 │ ErrZero │
└──────────┴────┴────┴──────┴──────────┘
跟着做
步骤 1:被测代码与第一个测试
$ go mod init example.com/calc
calc.go:
package calc
import "errors"
var ErrDivideByZero = errors.New("除数不能为零")
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivideByZero
}
return a / b, nil
}
calc_test.go(注意文件名后缀):
package calc // 与被测代码同包,可访问私有函数
import (
"errors"
"testing"
)
func TestDivide(t *testing.T) {
got, err := Divide(10, 2)
if err != nil {
t.Fatalf("不期望的错误: %v", err)
}
if got != 5 {
t.Errorf("Divide(10,2) = %v, 期望 5", got)
}
}
$ go test ./...
ok example.com/calc 0.3s
规则速记:文件名 *_test.go;函数名 Test 开头 + 首字母大写;参数固定 t *testing.T;t.Errorf 标记失败但继续,t.Fatalf 标记失败并终止当前测试。
步骤 2:表驱动测试 + 子测试
把多组用例排进结构体切片,一个函数测完所有场景:
package calc
import (
"errors"
"testing"
)
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr error
}{
{"正常除法", 10, 2, 5, nil},
{"负数运算", -9, 3, -3, nil},
{"小数结果", 1, 4, 0.25, nil},
{"除以零", 1, 0, 0, ErrDivideByZero},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { // 每个用例一个子测试
got, err := Divide(tt.a, tt.b)
if tt.wantErr != nil {
if !errors.Is(err, tt.wantErr) {
t.Fatalf("期望错误 %v, 实际 %v", tt.wantErr, err)
}
return
}
if err != nil {
t.Fatalf("不期望的错误: %v", err)
}
if got != tt.want {
t.Errorf("Divide(%v, %v) = %v, 期望 %v", tt.a, tt.b, got, tt.want)
}
})
}
}