返回资讯中心
外部精选
软件工程
#开发者实时流

fakeredis 推出 FakeValkey:进程内 Valkey 测试替身

来源标题:Testing without a server: using FakeValkey, the in-memory test double for Valkey

fakeredis 项目发布 FakeValkey 类,它是 Valkey 命令集的纯 Python 内存实现,可作为 valkey-py 的客户端类在测试进程中直接使用,无需启动真实服务器。fakeredis 每月下载量约 4000 万次(2026 年 9 月),FakeValkey 继承自 valkey.Valkey,命令不经过网络。作者在文中说明了它如何跟进 Valkey 版本,以及何时仍应使用真实服务器。

Hacker News 最新gslin2026/11/4 · 今天18 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

每个与 Valkey 交互的应用都要在测试套件里回答同一个问题:服务器从哪来?

你可以在持续集成(CI)里启动一个真实的 valkey-server,这最准确,但每次运行都要多一个容器、一个端口和启动时间。

你也可以 mock 客户端,这很快,但 mock 只知道你告诉它的东西,所以它会与服务器逐渐偏离,却不会让任何一个测试失败。

测试替身介于两者之间:它在测试进程内运行,实现了服务器的行为。

我维护着 fakeredis,这是一个纯 Python、内存中的 Valkey 命令集实现,作为客户端类接入 valkey-py。它每月约有 4000 万次下载(2026 年 9 月)。

本文介绍它的 FakeValkey 类:如何使用它、它如何跟上 Valkey 的步伐、如何编写能跨 Valkey 版本站得住脚的测试、如何用它检查向 Valkey 的迁移,以及什么时候你应该改用真实服务器。

用 valkey extra 安装这个包,它会一并拉入 valkey-py:

pip install "fakeredis[valkey]"

FakeValkey 是 valkey.Valkey 的子类,所以任何接受 Valkey 客户端的代码都接受它。命令从不离开进程:没有网络连接,数据存在 Python 对象里。

from fakeredis import FakeValkey client = FakeValkey() client.set("greeting", "hello") client.get("greeting") # b'hello'

FakeAsyncValkey 对 valkey.asyncio.Valkey 做同样的事。

本文示例使用该库的 2.38.0 版本和 valkey-py 6.1.1,运行在 Python 3.14 上。

下面是一个登录限流器,允许每个用户每分钟尝试五次:

def allow_login(client, user_id, limit=5, window=60): key = f"login:{user_id}" attempts = client.incr(key) if attempts == 1: client.expire(key, window) return attempts <= limit

测试在生产代码传入真实客户端的地方传入 FakeValkey:

import fakeredis import pytest from ratelimit import allow_login def valkey_client(): return fakeredis.FakeValkey() def test_blocks_the_sixth_attempt(valkey_client): for _ in range(5): assert allow_login(valkey_client, "alice") assert not allow_login(valkey_client, "alice") assert 0 < valkey_client.ttl("login:alice") <= 60 def test_each_test_starts_empty(valkey_client): assert valkey_client.get("login:alice") is None

在 arm64 Linux 虚拟机上,pytest 报告两个测试共耗时 0.09 秒。

每个 FakeValkey 实例都有自己的内存服务器,所以第二个测试看不到第一个测试写入的任何键。

当两个客户端需要看到同一份数据时,比如一个 Web 处理器和一个后台 worker,给它们同一个 FakeServer:

from fakeredis import FakeServer, FakeValkey server = FakeServer(server_type="valkey") web = FakeValkey(server=server) worker = FakeValkey(server=server) web.set("job:42", "queued") worker.get("job:42") # b'queued'

如果被测代码通过主机和端口自己建立连接,TcpFakeServer 会通过 TCP 提供同样的假实现。

FakeValkey 只有在行为像服务器时才有用,而 Valkey 一直在发布新版本。

该库通过把测试套件跑两遍来自检:每个测试都用的 client fixture 被参数化,同时产出假客户端和连接真实服务器的客户端,覆盖协议版本 2 和 3。当真实服务器是 Valkey 时,fixture 选择 FakeValkey 和 valkey.Valkey,所以同一条断言会在两者上运行。

CI 矩阵让这套测试跑在 valkey/valkey:9.1 和 valkey/valkey:8.1.8 镜像上。

只适用于某些版本的测试会带上标记,例如 @pytest.mark.supported_server_versions(min_valkey_ver="9")。

这样看,这套测试就是一份可执行的规范,记录了观察到的 Valkey 行为。对 Valkey 通过、对假实现失败的测试,就是一份针对假实现的 bug 报告,而且复现步骤已经附上了。

测试依赖的某些行为并不在命令文档里,假实现只能逐个案例去匹配。这些案例各自提示了你写测试时该养成的一个习惯:

这个库在一个客户端接口后面模拟了多个服务器,所以你可以先让现有测试跑在 Valkey 的行为上,再改动任何基础设施。

把你测试已经在用的 client fixture 参数化:

import fakeredis import pytest def client(request): if request.param == "baseline": return fakeredis.FakeRedis(version=(8, 8)) # example: use the server version you run today if request.param == "valkey-server": return fakeredis.FakeRedis(server_type="valkey") return fakeredis.FakeValkey()

现在每个测试运行三次,每个变体在进程内模拟一个迁移步骤;它们都不连接真实服务器:

这些变体告诉你迁移前该往哪里看;再按下一节所述,用真实 Valkey 服务器确认结果。

下面这段代码调用 INCREX 命令,基线服务器支持它,而目前还没有正式发布的 Valkey 版本支持。

Valkey 9.2(目前是候选发布版)加入了自己的 INCREX;fakeredis 2.38.0 在 server_type="valkey" 时不模拟它。

对三个变体运行它的测试,就能看出哪个迁移步骤会把它弄坏。

def count_request(client, user_id): value, _ = client.increx(f"requests:{user_id}", ex=60, enx=True) return value def test_count_request_sets_the_window_once(client): assert count_request(client, "alice") == 1 assert count_request(client, "alice") == 2 assert 0 < client.ttl("requests:alice") <= 60

这个测试在 baseline 上通过。

在 valkey-server 上,假实现回复 unknown command 'increx',正如 Valkey 9.1 服务器拒绝未知命令一样;而在 valkey-client 上,valkey-py 没有 increx() 方法。

用 FakeValkey 测试应用逻辑:读写键、设置过期时间、运行事务、脚本和 pub/sub 的代码,每个测试都应从空状态开始并在毫秒级完成。

支持的 commands 文档还涵盖 JavaScript Object Notation(JSON)、布隆过滤器和时间序列命令。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →