test
0.1 · go test 命令(go help test)#
用法:go test [build/test flags] [packages] [flags & test binary flags]
- 对指定包做自动化测试,输出每包摘要(
ok/FAIL+ 耗时),失败时再打详细输出。 - 会编译
*_test.go;以_或.开头的文件被忽略。testdata目录被忽略,可放测试数据。 - 构建前会跑 go vet 子集(atomic、bool、printf、tests 等),有问题则不运行测试。
-vet=off关闭,-vet=all全开。 - 测试的 stdout/stderr 会汇总到
go test的 stdout。
两种模式:
| 模式 | 触发 | 缓存 |
|---|---|---|
| 本地目录 | 无包参数(go test / go test -v) | 否 |
| 包列表 | 显式包(go test . / ./... / math) | 是 |
缓存:仅当仅用可缓存标志时生效(如 -run、-bench、-v、-count、-timeout 等)。禁用缓存常用 -count=1。
go test 自有标志:-c 只编译不运行;-o file 指定二进制路径;-args 后参数原样传测试二进制(包列表须在 -args 前);-json 输出 JSON;-exec xprog 用 xprog 跑二进制。
go test -v -args -x # -args 后原样传测试二进制
pkg.test -test.v -test.cpuprofile=prof.out # 直接跑二进制时用 test. 前缀
0.2 · testing 包(go doc testing)#
0.2.1 · 四种函数形式
| 形式 | 用途 |
|---|---|
func TestXxx(*testing.T) | 单元测试,用 t.Error/t.Fail 报告失败 |
func BenchmarkXxx(*testing.B) | 基准,提供 -bench 时顺序执行 |
func FuzzXxx(*testing.F) | 模糊测试,随机/变异输入 |
func ExampleXxx() | 示例,用 stdout + // Output: 校验 |
- 测试文件:
*_test.go,普通 build 不包含。同包可访问未导出符号;package pkg_test为黑盒,只能访问导出符号。 - 更多:
go help test、go help testflag。
0.2.2 · TestXxx#
- 跳过:
t.Skip();例if testing.Short() { t.Skip("...") },配合go test -short。
func TestTimeConsuming(t *testing.T) {
if testing.Short() {
t.Skip("short 模式下跳过")
}
// ...
}
- 子测试:Subtests —
t.Run(name, func(t *testing.T) { ... }),可表驱动、共用 setup/tear-down。名字为「顶层/子名」层级。
func TestFoo(t *testing.T) {
// <setup code>
t.Run("A=1", func(t *testing.T) { ... })
t.Run("A=2", func(t *testing.T) { ... })
t.Run("B=1", func(t *testing.T) { ... })
// <tear-down code>
}
go test -run Foo/A= # 顶层匹配 Foo,子测试匹配 A=
go test -run /A=1 # 所有顶层的子测试匹配 A=1
- -run:未锚定正则,按
/分层;go test -run=FuzzFoo/种子id可调试种子。 - 并行:子测试里
t.Parallel();父测试等所有子测试完成。可用t.Run("group", ...)再在外部做 tear-down。
0.2.3 · BenchmarkXxx#
- 新写法用
b.Loop()包住被测代码;旧写法for i := 0; i < b.N; i++,昂贵初始化前调b.ResetTimer()。 - 并行:
b.RunParallel(func(pb *testing.PB) { for pb.Next() { ... } }),常配-cpu。 - 子基准:
b.Run(name, func(b *testing.B) { ... })。
func BenchmarkRandInt(b *testing.B) {
for i := 0; i < b.N; i++ { rand.Int() }
}
// 输出 BenchmarkRandInt-8 68453040 17.8 ns/op 表示循环 68453040 次,约 17.8 ns/op
func BenchmarkBigLen(b *testing.B) {
big := NewBig()
b.ResetTimer()
for i := 0; i < b.N; i++ { big.Len() }
}
并行基准(常配 -cpu):
func BenchmarkTemplateParallel(b *testing.B) {
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
b.RunParallel(func(pb *testing.PB) {
var buf bytes.Buffer
for pb.Next() {
buf.Reset()
templ.Execute(&buf, "World")
}
})
}
子基准(Sub-benchmarks):b.Run(name, func(b *testing.B) { for i := 0; i < b.N; i++ { ... } }),多组参数可写表驱动。
- 规范与工具:Go Benchmark Data Format、golang.org/x/perf/cmd(如 benchstat)。
0.2.4 · FuzzXxx#
模糊测试:随机/变异输入找 bug。种子:(*F).Add 或 testdata/fuzz/<Name>;target 参数类型与 Add 一致。无效输入可 t.Skip()。失败输入会写回 testdata/fuzz/<Name>。
func FuzzHex(f *testing.F) {
for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { f.Add(seed) }
f.Fuzz(func(t *testing.T, in []byte) {
enc := hex.EncodeToString(in)
out, err := hex.DecodeString(enc)
if err != nil { t.Fatalf("%v: decode: %v", in, err) }
if !bytes.Equal(in, out) { t.Fatalf("%v: round trip", in, out) }
})
}
Add支持:string, []byte, 整数/浮点/bool,及 BinaryMarshaler/TextMarshaler。- 文档:go.dev/doc/fuzz、draft-fuzzing、Fuzzing in Go。最佳实践。
0.2.5 · ExampleXxx#
- 用
// Output:或// Unordered output:与 stdout 比较(忽略首尾空白)。无注释则只编译不执行。 - 命名:
Example、ExampleF、ExampleT、ExampleT_M;多示例加后缀如Example_suffix。
func ExamplePrintln() {
Println("The output of\nthis example.")
// Output: The output of
// this example.
}
func ExamplePerm() {
for _, v := range Perm(4) { fmt.Println(v) }
// Unordered output: 4
// 2
// 1
// 3
// 0
}
0.2.6 · TestMain#
若定义 func TestMain(m *testing.M),生成的 main 调用它而非直接跑测试;在 main goroutine 里 setup/tear-down 并 m.Run()。用标志时需在 TestMain 里 flag.Parse()。
func TestMain(m *testing.M) {
// flag.Parse() 若依赖命令行标志
os.Exit(m.Run())
}
0.3 · Testing flags(go help testflag)#
| 标志 | 说明 |
|---|---|
-run regexp | 只运行匹配的测试 |
-bench regexp | 运行匹配的基准;默认不跑,-bench . 跑全部 |
-benchtime t | 基准时长,默认 1s;Nx 为 N 次 |
-fuzz regexp / -fuzztime t | 模糊测试及时长 |
-cpu 1,2,4 | GOMAXPROCS 组合 |
-count n | 执行次数 |
-parallel n | t.Parallel() 的最大并发数 |
-timeout d | 超时 panic,0 禁用 |
-v | 详细输出 |
-short | 可配合 testing.Short() 跳过耗时测试 |
-cover / -covermode / -coverpkg | 覆盖率 |
-benchmem | 基准内存分配 |
-cpuprofile / -memprofile | 写出 profile 给 go tool pprof |
对 test 二进制可用 test. 前缀,如 pkg.test -test.v -test.cpuprofile=out。
0.4 · 表驱动与并行示例
// 表驱动:每行输入+预期,可带 name
tests := []struct {
input, sep string
want []string
}{
{"a/b/c", "/", []string{"a", "b", "c"}},
{"abc", "/", []string{"abc"}},
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(tc.want, got) {
t.Fatalf("expected: %v, got: %v", tc.want, got)
}
})
}
// 并行子测试(注意 tc := tc 避免闭包捕获)
for _, tc := range tests {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
// ...
})
}
0.5 · pkg for test#
- 断言:stretchr/testify(assert、require)
- HTTP:net/http/httptest;外部 API mock:h2non/gock、jarcoal/httpmock
- DB:DATA-DOG/go-sqlmock;Redis:alicebob/miniredis
- Mock/Stub:Mock=接口替换,Stub=函数/占位。区别、Mocks Aren’t Stubs。golang/mock、prashantv/gostub;bouk/monkey(需
-gcflags=-l,非线程安全)、agiledragon/gomonkey - BDD:goconvey(Convey/So、SkipConvey、FocusConvey)、Assertions
HTTP 测试示例(httptest + 表驱动):
func Test_helloHandler(t *testing.T) {
tests := []struct{ name, param, expect string }{
{"base", `{"name":"liwenzhou"}`, "hello liwenzhou"},
{"bad", "", "we need a name"},
}
r := SetupRouter()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("POST", "/hello", strings.NewReader(tt.param))
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]string
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, tt.expect, resp["msg"])
})
}
}
0.6 · coverage / tricks / 可测代码#
- 覆盖率:
go test -cover;常见要求约 80%。 - 技巧:inline 接口(caller 定义接口并注入,便于 mock);辅助断言(assert/ok/equals)见 benbjohnson/testing、Go 测试技巧、povilasv。
- 集成测试:Integration testing with Docker
- 可测设计:接口抽象 + 依赖注入(如 google/wire);SOLID。Absolute unit test、Learn Go with Tests、TestComments。