测试 panic 必须用 defer+recover,因直接调用 recover() 总返回 nil;正确做法是先 defer 匿名函数内 recover(),再触发 panic;recover 返回 interface{},需先判空再类型断言;子 goroutine panic 无法被外层捕获;推荐用 testify/assert.PanicsWithValue 简化断言,但仅限当前 goroutine;panic 值类型不匹配时应先 assert.NotNil 再用 assert.IsType 或 errors.As 安全校验;调试 recover 失效时可用 runtime/debug.Stack 打印堆栈定位。

测试 panic 是否发生,必须用 defer + recover
直接在测试函数里调用 recover() 永远返回 nil,因为 panic 已结束、调用栈已展开完毕。真正有效的做法是:在触发 panic 前注册一个 defer,并在其内部调用 recover() 捕获值。
常见错误写法:panic("boom"); r := recover() —— 这段代码根本不会执行到 recover()。
- 正确结构:先
defer func() { r := recover(); ... }(),再调用可能 panic 的函数 -
recover()返回interface{},必须先判空再做类型断言,否则r.(string)在非 string panic 时会二次 panic - 子 goroutine 中的 panic 无法被外层
defer捕获;若被测函数启了 goroutine 并 panic,测试会卡住或崩溃,此时需改用集成测试或重构逻辑
用 testify/assert.PanicsWithValue 简化 panic 断言
原生 testing.T 只能判断 panic 是否发生,无法校验 panic 值内容。testify/assert.PanicsWithValue 封装了 defer+recover,一行就能断言 panic 类型和值。
前提是:被测函数必须在当前 goroutine 中 panic,且 panic 值可比较(如 string、error、可导出字段的 struct)。
- 校验字符串 panic:
assert.PanicsWithValue(t, "division by zero", func() { divide(1, 0) }) - 校验 error panic:
assert.PanicsWithError(t, "invalid input", func() { riskyFunc() }) - 注意:
PanicsWithValue底层仍依赖recover(),所以它同样无法捕获 goroutine 内 panic - CI 环境需确保
go.mod已包含github.com/stretchr/testify,否则go test报错找不到包
panic 值类型不匹配时别强转,先用 assert.NotNil + 类型检查
panic 可以传任意类型:panic("msg")、panic(errors.New("x"))、panic(MyError{Code: 404})。只断言 r != nil 不够,但直接 r.(MyError) 又容易因类型不符导致测试崩溃而非失败。
- 安全做法:先
assert.NotNil(t, r),再用assert.IsType(t, MyError{}, r)或assert.True(t, errors.Is(r, expectedErr))(若 panic 是 error) - 若 panic 是自定义 error,推荐用
errors.As(r, &target)解包后比字段,比字符串匹配更稳定 - 避免在断言中调用可能 panic 的函数(如
json.Marshal(r)),否则测试直接退出,看不到原始 panic 值
调试 recover 失效?用 runtime/debug.Stack 定位真实 panic 位置
当 recover() 总是返回 nil,但你确认 defer 已注册、被测函数也确实 panic 了,大概率是 panic 发生在别的 goroutine,或 recover() 被包裹在另一层 panic 里。
此时唯一可靠手段是打印堆栈:
- 在
defer函数里加:buf := make([]byte, 2048); runtime.Stack(buf, false); t.Log(string(buf)) - 输出会显示 panic 真实发生位置、调用栈深度、goroutine ID —— 一眼看出是不是子 goroutine 导致的
- 别依赖 “只要 defer 写对就一定 recover 到”;Go 的 panic/recover 是 goroutine 局部机制,跨 goroutine 不传递
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











