Ginkgo 的 Describe/It 必须包裹在 TestXxx 函数中并调用 RunSpecs 才能注册执行,否则 panic 或无测试运行;RegisterFailHandler(Fail) 需在 RunSpecs 前调用以确保断言失败时输出上下文;并行测试需区分 BeforeSuite(全局一次)与 BeforeEach(每个 It 前);异步测试必须使用 Done 参数显式通知完成。

Ginkgo 的 Describe/It 必须包裹在 TestXxx 函数里,否则直接 panic —— 这不是语法问题,是框架启动失败。
为什么 Describe 写在包顶层就 panic
Ginkgo 不靠 go test 自动发现测试函数,它靠 RunSpecs 扫描当前 TestXxx 函数体内注册的 spec 树。没调 RunSpecs,Describe 和 It 就只是普通变量赋值,根本不会被注册。
- 常见错误现象:
go test输出ok ./pkg 0.001s,但断言完全没执行;或 panic 报failed to run spec: no tests registered - 必须在每个
_test.go文件里写一个func TestXxx(t *testing.T),且只调一次RunSpecs(t, "Suite Name") - 不能把
RunSpecs放进init()或包级变量初始化里——会破坏go test生命周期 - 如果用
ginkgo run命令运行,则不用手写TestXxx,但此时不能再混用go test
RegisterFailHandler(Fail) 漏掉会导致断言失败不报具体值
Gomega 的 Expect(...).To(Equal(x)) 失败时,不会自动打印实际值和期望值,它依赖你传给 RegisterFailHandler 的 handler 来抛出带上下文的错误。漏注册,就会看到空 panic 或误导性的 test timed out。
- 必须在
TestXxx函数里、RunSpecs之前调用RegisterFailHandler(Fail) -
Fail来自github.com/onsi/ginkgo/v2,不是t.Fail—— 写成RegisterFailHandler(t.Fail)会 panic - 用
ginkgo run时Fail默认已注册;但手动go test时这步绝不能省
并行测试下 BeforeSuite 和 BeforeEach 混用引发状态污染
BeforeSuite 只执行一次(适合 DB 连接池、mock HTTP server),BeforeEach 每个 It 前都执行(适合重置 map、临时文件、mock 对象)。错把本该隔离的资源放 BeforeSuite,多个 It 就会争抢同一份状态。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 典型现象:单测全过,加
-p 4就随机失败;出现connection refused或key already exists - 全局共享资源(如
httptest.NewServer、*sql.DB)可放BeforeSuite,但必须确保线程安全或带锁访问 - 每个测试独占资源(如
map[string]string{}、service := NewService())必须在BeforeEach里初始化 - 避免在
BeforeSuite里写入可变全局变量,比如var db *sql.DB然后在It里改它的字段
It 里做异步操作必须接收 Done 参数并显式关闭
Ginkgo 默认等待 It 闭包返回,不感知 goroutine。如果你启动 goroutine 但没告诉框架“我还没完”,测试会在 1 秒后超时失败。
- 正确写法:
It("does async work", func(done Done) { go func() { defer close(done); /* your logic */ }() }) - 必须用
defer close(done)或直接close(done),否则测试永远卡住 - 不能在
It里用time.Sleep等待异步完成——这是反模式,应改用 channel 或done - 超时时间可通过
It("...", Label("slow"), func(done Done) { ... }, 5.0)扩展,单位秒
最容易被忽略的是:所有 Describe/It 代码块本身不执行任何逻辑,它们只是注册函数;真正触发执行的是 RunSpecs 启动的调度器。没这一步,整套 BDD 结构就是一堆死代码。


















