测试中捕获panic必须用defer+recover在同goroutine内提前注册,recover()需在defer函数体内直接调用并判空;子goroutine panic无法被外层捕获,应单独测试或内部加recover。

测试函数是否 panic 必须用 defer + recover 封装
直接在测试函数里调用被测函数再写 recover() 永远得不到 panic 值,因为 panic 已经结束、栈已展开完毕。真正有效的捕获点,是在 panic 触发前就注册好 defer,且该 defer 必须在同一个 goroutine 中执行。
常见错误写法:func TestX(t *testing.T) { f(); r := recover() } —— 这行 recover() 根本不会执行,测试直接崩溃。
正确结构是:先注册 defer func() { r := recover() },再触发 panic(比如调用被测函数),让 panic 被这个 defer 捕获。
- 必须把被测逻辑包进一个立即执行的匿名函数里,避免污染外层作用域
- 不要在测试函数顶层写
defer,它会在测试结束时才运行,起不到捕获作用 -
recover()返回interface{},断言前务必先判空,否则r.(string)会 panic 而非失败
用 testify/assert.PanicsWithValue 断言 panic 内容
原生 testing 包只能判断 panic 是否发生,无法校验 panic 的值。这时候 testify/assert 提供了更安全的封装:PanicsWithValue 和 PanicsWithError。
PanicsWithValue(t, func() { f() }, "expected msg") 会内部做 recover() 并用 == 比较返回值,适用于字符串、整数等可比较类型。
PanicsWithError(t, "EOF", func() { f() }) 则只比对 Error() 方法返回的字符串,适合 error 类型 panic。
- 若 panic 的是自定义结构体,且未实现
Error(),必须确保该结构体可比较,否则PanicsWithValue会 panic - 所有这些断言底层仍是
recover(),所以同样不适用于子 goroutine 中的 panic - 使用前需
import "github.com/stretchr/testify/assert"
子 goroutine 的 panic 无法被外层 recover 捕获
这是最常踩的坑:如果被测函数内部启动了 goroutine 并在其中 panic,主 goroutine 的 defer + recover() 完全无效。测试会卡住、超时,或直接崩溃退出,且无堆栈输出。
现象示例:go func() { panic("in goroutine") }() 导致测试进程终止,而不是失败。
- 解决方法一:把 goroutine 内部逻辑抽出来单独测试(不启 goroutine)
- 解决方法二:在 goroutine 内部加自己的
defer func() { recover() }() - 真实场景中常见于 HTTP handler、worker pool 等异步逻辑,测试时必须模拟同构执行环境
调试 recover 失效时用 runtime/debug.Stack()
当 recover() 返回 nil 但你确认 defer 已注册,大概率是 panic 发生在别的 goroutine,或者 defer 函数里又 panic 了。
此时唯一可靠手段是打印当前 goroutine 的完整堆栈:buf := make([]byte, 2048); runtime.Stack(buf, false),放在 defer 函数里执行,看 panic 真实发生位置。
- 别依赖日志或 print 输出——panic 可能发生在任意时刻,堆栈才是唯一可信线索
- 注意
runtime.Stack(buf, false)只打当前 goroutine;如需全部,用true,但输出可能过长 - 这种调试方式应在本地复现时使用,CI 中建议用
go test -v -race辅助排查竞态
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











