go test默认不控制精度,浮点比较不能用==,须用math.Abs(got - want) ≤ tolerance规避精度依赖,因编译器优化、寄存器精度等会导致微小偏差。

go test 默认不控制精度,但测试中对浮点、时间、随机性等场景的断言极易因精度问题误报失败。关键不是“提高精度”,而是规避精度依赖。
浮点比较不能用 ==,必须用误差容忍
直接 if got == want 在 float64 上几乎必败,哪怕计算路径完全一致——编译器优化、寄存器精度、x87 与 SSE 指令差异都会导致微小偏差。
正确做法是用 math.Abs(got - want) :
- 常见 tolerance:1e-9(科学计算)、1e-6(一般业务)、
1e-3(时间毫秒级差值) - 别写死
0.001,用const epsilon = 1e-6显式声明语义 - 第三方库如
github.com/google/go-cmp/cmp支持cmp.Comparer(func(f1, f2 float64) bool { return math.Abs(f1-f2)
时间比较要剥离时区和纳秒抖动
time.Now() 或 time.Unix(…) 生成的时间值在纳秒级几乎不可能相等,尤其跨 goroutine 或 mock 时。
立即学习“go语言免费学习笔记(深入)”;
推荐策略:
- 测试中统一用
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)这类确定值,而非实时调用 - 比较两个
time.Time时,用t1.Equal(t2)(注意它比 == 更鲁棒),或先.Truncate(time.Second)再比 - 若逻辑依赖“是否在某时间之后”,用
t.After(base.Add(-1 * time.Second))而非t.After(base),留出安全余量
随机性必须可重现,不能靠 seed 碰运气
用 rand.New(rand.NewSource(42)) 是基础,但还不够——很多 Go 标准库函数(如 math/rand.Perm、strings.ReplaceAll 的某些实现)内部仍可能用全局 rand 或隐式时间 seed。
实操要点:
- 所有随机行为必须显式接收
*rand.Rand参数,禁止调用rand.Intn等全局函数 - 测试用例中传入固定 seed 的
*rand.Rand,并在每个 case 开头重置:r.Seed(42) - 若被测函数调用了未导出的随机逻辑(如第三方库),需通过 interface 抽象并注入可控实现
JSON/YAML/HTTP body 断言优先比结构,而非原始字节
直接 assert.Equal(t, string(resp.Body.Bytes()), expectedJSON) 极易因空格、字段顺序、浮点格式(1.0 vs 1)失败。
更稳的方式:
- 将响应 body 反序列化为 struct,再用
cmp.Equal(got, want)比较(自动忽略零值、忽略未导出字段) - 若必须比 JSON 字符串,先用
json.MarshalIndent格式化两边,再比 - HTTP 测试中,用
httptest.NewRecorder()后检查rec.Code和rec.Header().Get("Content-Type"),比 body 更早暴露问题
真实项目里,精度失控往往藏在最不起眼的断言里:一个没截断的 time.Now().UnixNano()、一次没传 *rand.Rand 的 shuffle、一段用 == 比 float 的旧逻辑——它们不会报错,只会让测试偶尔红一下,然后被 // TODO: flaky, skip for now 注释掉。


















