quick.Check 总在第0次失败是因为默认仅运行100次且遇panic或false立即终止;需防御边界输入、用自定义Generator覆盖算法边界、理解property-based testing局限并适时切换table-driven测试。

quick.Check 为什么总在第0次就失败?
因为 quick.Check 默认只运行 100 次,且一旦某次输入触发 panic 或返回 false,就立刻终止并报错——它不重试,也不跳过。很多人误以为“随机=覆盖广”,结果发现刚跑就挂,其实是被第一个边界值击穿了。
- 检查你的
f函数是否对空输入、极大数、负数等未做防御(比如除零、切片越界、整数溢出) - 确保
f的签名严格为func(input) bool,不能带额外参数或返回 error - 临时把
quick.Config{MaxCount: 10}设小一点,配合log.Println打印每次输入,快速定位崩在哪组数据
如何让 random test 真正覆盖算法边界?
默认的 quick.Value 生成器对 int 是 [-1e6, 1e6],对 string 是长度 ≤ 10 的 ASCII 随机串——这对很多算法(比如 RSA 密钥生成、大数 GCD、UTF-8 解析)完全不够用。
- 用自定义
Generator替换:实现Generate(rand *rand.Rand, size int) reflect.Value,控制数值范围或构造特定结构(如非空 slice、合法 UTF-8 字符串) - 对浮点数测试,避免用默认
float64生成器(它会产生 NaN/Inf),改用rand.Float64()*100 - 50限定区间 - 若算法依赖有序性(如二分查找),需手动生成已排序 slice,而非靠运气撞上
testing/quick 和 property-based testing 的本质区别
testing/quick 不是 Haskell 的 QuickCheck,它没有 shrinking(自动缩小反例)、没有类型驱动推导、也不支持复合条件组合(比如 “当 a > b 时,f(a,b) == f(b,a)”)。它只是“跑够次数 + 断言布尔结果”的轻量封装。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 别指望它自动简化失败用例——失败后得自己用
quick.CheckEqual或手写循环从最小 size 开始二分排查 - 多个约束要手动拼:比如“生成两个非零 int”,得写
if a == 0 || b == 0 { return quick.Generator(nil) }并返回 nil 表示放弃 - 它和
go test -bench无任何集成,性能敏感路径得另起 benchmark 单独压测
什么时候该停用 quick.Check?
当你发现 80% 的失败都来自同一类输入(比如全零 slice、nil 指针、time.Time 零值),或者需要验证输出精度(如浮点误差 ≤ 1e-9)、副作用(如文件写入、goroutine 启动)时,quick.Check 就力不从心了。
立即学习“go语言免费学习笔记(深入)”;
- 转用
table-driven tests显式枚举关键边界:min/max 值、临界长度、特殊编码字节 - 对浮点算法,用
cmp.Equal(got, want, cmpopts.EquateApprox(1e-9))替代布尔断言 - 涉及并发或状态变更的逻辑,必须写同步的单元测试,
quick的并发随机执行会掩盖竞态
真正难的不是生成随机数据,而是理解算法在哪些数学性质下必须成立——然后把那些性质翻译成可执行的布尔断言。没想清楚这个,再多随机也没用。

















