正确方式是用 miniredis.RunT(t) 启动并动态获取 srv.Addr() 地址,每次测试开头调用 srv.FlushAll() 清空数据,避免硬编码端口、复用实例或依赖其模拟 WATCH/SCAN 等边界行为。

直接用 miniredis.RunT(t) 启动,再把 srv.Addr() 喂给 redis.NewClient(),就能跑通 90% 的 Redis 单元测试;但硬写 "127.0.0.1:6379"、漏调 FlushAll()、或指望它模拟 WATCH/SCAN 边界行为,测试就不可靠。
启动和地址获取必须动态
miniredis 不监听固定端口,每次运行都绑定随机空闲端口。写死 "127.0.0.1:6379" 必然报 dial tcp 127.0.0.1:6379: connect: connection refused。
- 正确方式:调用
srv := miniredis.RunT(t)(推荐)或srv := miniredis.NewMiniRedis(); srv.Start(),再用srv.Addr()拿地址 -
srv.Addr()返回类似"127.0.0.1:52341"的字符串,可直接塞进redis.Options{Addr: ...} - 别用
miniredis.MustRun()在并发测试中——它不自动注册t.Cleanup,defer srv.Close()在t.Parallel()下会错乱关闭
测试数据隔离得手动做
同一个 miniredis 实例复用时,数据不会自动清空。上一个 test 写了 "user:1",下一个 test 就能读到——这不是 bug,是设计如此。
- 每个 test 开头加
srv.FlushAll(),它是同步、线程安全的,比新建实例更轻量 - 避免在
init()或包级变量里复用单个实例,否则多个 test 并发跑时状态互相污染 - 预设数据优先走
srv.Set("k", "v")或srv.HSet("h", "f", "v"),比用 client 发命令更快、不抛错、也不受 pipeline 缓存干扰
go-redis v8 初始化容易 panic
redis.NewClient() 在 v8 版本对 Addr 字段极其敏感:为空、为 nil、格式不对都会 panic,错误信息常是 invalid address 或直接崩溃。
立即学习“go语言免费学习笔记(深入)”;
-
Addr必须是非空字符串,且只能是"host:port"格式(不支持 unix socket、sentinel、cluster) - 别传
nil或空字符串给redis.Options;哪怕只填Addr,其余字段留默认即可 - 若代码用了
redis.UniversalClient(如适配哨兵),miniredis不支持——必须切回redis.NewClient()
别拿它测真实 Redis 的边界逻辑
miniredis 是内存模拟器,不是 Redis 全量镜像。它不模拟网络延迟、连接中断、命令解析错误、或部分原子性语义。
-
WATCH + EXEC在miniredis中永远成功,真实 Redis 可能返回nil;别靠它验证乐观锁冲突 -
SCAN游标不迭代分页,SCAN 0 MATCH *可能一次性返回全部 key;分页逻辑得靠集成测试 - 超时逻辑照样得测:真实 client 默认带 5s 超时,而
miniredis响应在微秒级。要用ctx, _ := context.WithTimeout(..., time.Microsecond)主动触发超时路径
最易被忽略的是:你写的测试通过了,不代表线上不出问题——WATCH、SCAN、pipeline 错误传播、EXPIRE 时间推进 这些点,miniredis 都不保真,得补集成测试兜底。


















