☰
twemproxy 测试体系全指南:基于 nosetests 的 Redis/Memcached 集成测试与 C 单元测试实战
2026/10/7 6:15:28 网站建设 项目流程
  • 后端

【免费下载链接】twemproxy

A fast, light-weight proxy for memcached and redis

项目地址:https://gitcode.com/gh_mirrors/twe/twemproxy
点击查看免费下载

导读

本文以仓库 tests/README.rst 为骨架,系统讲解 twemproxy(nutcracker,一款面向 memcached 与 redis 的轻量级代理)的完整测试方案:包括基于 Docker 的一键 CI 脚本、基于 nosetests 的 Python 集成测试环境搭建与用例编写、环境变量调参方法,以及基于 src/test_all.c 的 C 单元测试。读完本文,你将掌握从零搭建 twemproxy 测试环境、运行并扩展测试用例、借助环境变量控制代理启动参数、以及排查测试失败问题的完整能力。


一、测试体系概览:三层结构

twemproxy 仓库中的测试设施分为三层,各自职责清晰:

层次载体说明
编译检查 / C 单元测试src/test_all.c、make check纯 C 实现,不依赖 Python,验证哈希算法、配置解析等底层正确性
Python 集成测试tests/ 目录 + nosetests真实拉起 redis-server / memcached / nutcracker 进程做端到端验证
Docker 一键 CItest_in_docker.sh + ci/Dockerfile一条命令完成上面两类测试

这套 Python 测试设施借鉴自 idning/redis-mgr 项目(README 中已注明),整体测试树结构如下:

tests/ ├── _binaries/ # 被测二进制集中存放(nutcracker、redis、memcached 等) ├── conf/ # 测试期进程的配置文件模板与常量 │ ├── conf.py # 二进制路径常量(BINARYS 字典) │ ├── control.sh # 进程启停脚本模板 │ ├── redis.conf # Redis 服务端配置模板 │ └── sentinel.conf # Redis Sentinel 配置模板 ├── lib/ │ ├── server_modules.py # 测试服务对象:RedisServer / RedisSentinel / Memcached / NutCracker │ └── utils.py # 公共工具:日志、断言、模板替换等 ├── log/ # 测试日志输出目录(默认 log/t.log) ├── test_memcache/ # Memcached 协议集成测试 ├── test_redis/ # Redis 协议集成测试(auth、pipeline、mget/mset 等) ├── test_system/ # 系统级测试(如 reload) ├── README.rst # 本文所依据的官方测试说明 └── nosetests_verbose.sh # 超详细日志模式运行脚本

二、Docker 环境下跑全量测试

仓库根目录的 test_in_docker.sh 可在 Docker 中依次执行 twemproxy 的编译检查、C 单元测试以及本目录(tests/)的全部集成测试。用法如下:

REDIS_VERSION=6.2.4 ./test_in_docker.sh $REDIS_VERSION

脚本细节(test_in_docker.sh):

  • 默认REDIS_VER=3.2.11;传入一个参数时覆盖该版本,传入多个参数则打印用法并退出。
  • 用git describe --always得到 TAG,构造镜像名twemproxy-build-nutcrackerci-$REDIS_VER-$TAG。
  • docker build -f ci/Dockerfile构建镜像(REDIS_VER作为 build-arg 传入)。
  • 第一轮docker run在/usr/src/twemproxy/src目录执行make test_all && ./test_all,即编译并运行 C 单元测试;失败会记录UNIT_TEST_FAIL=yes。
  • 第二轮docker run执行nosetests -v test_redis test_memcache test_system,即运行全部 Python 集成测试。
  • 若单元测试失败则最终以非零码退出;集成测试失败则由set -e直接中断。

对应的 ci/Dockerfile 以centos:7为基础,安装了gcc make autoconf automake libtool等构建工具、memcached、socat(供 memcached 存活探测)、python36u与pip,随后安装 nosetests 及 redis-py、python-memcached 依赖,并通过源码编译指定版本的 redis(含 redis-server、redis-sentinel、redis-cli)。镜像内将编译产物src/nutcracker软链到tests/_binaries/nutcracker,并把 redis/memcached 系列二进制复制进tests/_binaries/,最后以daemon用户运行,保证测试日志可写。


三、环境准备:依赖安装与二进制目录

3.1 安装 Python 依赖

集成测试基于 nosetests,需要三个关键依赖(注意 redis-py 必须是 3.0 或更新版本):

pip install nose pip install redis==3.5.3 # redis-py ≥ 3.0(README 原命令以 git 安装并锁定 3.5.3) pip install python-memcached==1.58 # 提供 memcache 模块

说明:README 原始命令以git+https://...@版本的方式精确锁定源码版本,上表给出的是等价 PyPI 包名版本;ci/Dockerfile 中同样固定了redis-py@3.5.3与python-memcached@1.58。若你的环境无法访问对应源,可先用python3 -c "import redis, memcache"验证依赖是否就绪。

3.2 准备 _binaries 二进制目录

将以下二进制放入tests/_binaries/:

_binaries/ |-- nutcracker |-- redis-benchmark |-- redis-check-aof |-- redis-check-dump |-- redis-cli |-- redis-sentinel |-- redis-server |-- memcached

这些路径并非硬编码:测试通过 tests/conf/conf.py 中的BINARYS字典统一解析,例如REDIS_SERVER_BINS指向_binaries/redis-*(redis-server 与 redis-sentinel 共用通配符)、REDIS_CLI指向_binaries/redis-cli、NUTCRACKER_BINS指向_binaries/nutcracker。因此只要目录内二进制命名符合预期,替换版本只需覆盖文件即可。


四、运行集成测试

在tests/目录下执行(或直接运行 tests/nosetests_verbose.sh):

$ python3 -m nose -v test_del.test_multi_delete_on_readonly ... ok test_mget.test_mget ... ok ---------------------------------------------------------------------- Ran 2 tests in 4.483s OK

nosetests 会按模块顺序执行tests/下所有test_*.py,每个测试模块中的setup()/teardown()(见 tests/test_redis/common.py)负责拉起与回收 redis-server 和 nutcracker 进程:setup()依次对每个服务执行clean() -> deploy() -> stop() -> start(),teardown()则断言所有服务仍存活后逐一stop()。

4.1 使用 nosetests_verbose.sh 查看详细日志

测试目录中的 nosetests_verbose.sh 是一个超详细输出的辅助脚本:

#!/bin/bash -xeu if [[ $# == 0 ]]; then echo "Usage: $0 test_a [test_b, ...]" 1>&2 exit 1 fi # Print test logging to stderr export T_LOGFILE=- python3 -m nose -v --nologcapture --nocapture "$@"

要点:

  • 至少传入一个测试模块名(如./nosetests_verbose.sh test_redis.test_basic),脚本内部将T_LOGFILE设为-,把日志直接打到 stderr;
  • 附加--nologcapture --nocapture,使测试中的print与logging输出不被 nose 的日志捕获机制吞掉,方便排查。

五、新增测试用例

5.1 三步法:复制 → 修改 → 运行

README 给出的最简流程是复制一个现有用例再改写:

cp tests/test_del.py tests/test_xxx.py vim tests/test_xxx.py

在当前仓库中,实际用例按协议分目录组织(tests/test_redis/、tests/test_memcache/、tests/test_system/),因此更贴合现状的做法是参照同目录已有用例,例如复制 tests/test_redis/test_basic.py(涵盖 set/get、msetnx、ping/quit、Lua 慢请求、SIGSEGV 信号、stats 等)或 tests/test_redis/test_mget_mset.py(涵盖 mget/mset、大 key 批量、后端宕机、多键删除等),再改成你的场景。

5.2 可复用的测试基建

新增用例无需自己管理进程生命周期,tests/lib/server_modules.py 提供了四个服务对象:

  • RedisServer:以bin/redis-server conf/redis.conf启动,用redis-cli PING判断存活,支持SLAVEOF、INFO、认证(auth);
  • RedisSentinel:以bin/redis-sentinel conf/sentinel.conf启动,自动追加sentinel monitor段,支持SENTINEL FAILOVER手动故障转移;
  • Memcached:以bin/memcached -d -p $port启动,用echo "stats" | socat - TCP:$host:$port探测存活;
  • NutCracker:twemproxy 代理本体,通过 telnet 连接status_port(监听端口 + 1000)拉取 JSON 统计信息,并封装了reload()(发 SIGUSR1)、signal()、set_config()等运维操作。

每个对象都实现了统一的deploy() / start() / stop() / status() / _alive()生命周期;deploy()会依据 tests/conf/control.sh 模板生成{name}_control启停脚本(内部用pkill -9 -f '${runcmd}'停止进程,并在start()前ulimit -c unlimited以保留 core dump)。

以NutCracker为例,其自动生成的 nutcracker 配置(_gen_conf())完整复刻了生产可用的参数组合,可作为你手写配置文件时的参考模板:

$cluster_name: listen: 0.0.0.0:$port hash: fnv1a_64 distribution: modula preconnect: true auto_eject_hosts: false redis: $is_redis backlog: 512 timeout: 400 client_connections: 0 server_connections: 1 server_retry_timeout: 2000 server_failure_limit: 2 servers: - $host:$port:1 $server_name

同时其启动命令为bin/nutcracker -d -c $conf -o $logfile -p $pidfile -s $status_port -v $verbose -m $mbuf -i 1——这正是下文环境变量T_VERBOSE(-v)与T_MBUF(-m)注入代理的方式;若配置了redis_auth,模板还会自动追加redis_auth: $redis_auth行;若传入 sentinels,还会追加sentinels:段。

5.3 nose 装饰器与系统级用例示例

系统级测试 tests/test_system/test_reload.py 展示了@with_setup(_setup, _teardown)装饰器的用法:每个用例执行前跑_setup、执行后跑_teardown,用例内部通过裸 TCP 连接逐字节断言 RESP 响应(如send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n')),用于验证配置热加载期间旧连接在宽限期内的行为。该文件还以VERSION_SUPPORTING_RELOAD做版本门控,低于支持版本的代理直接跳过用例——这种写法在新增针对新功能的用例时值得借鉴。


六、环境变量速查:控制代理启动参数与测试规模

README 定义了四个环境变量,用于在不改代码的前提下调整 nutcracker 启动参数与测试数据规模:

环境变量作用默认值
T_VERBOSE以-v $T_VERBOSE启动 nutcracker,控制日志详细度README 标注 default:4;tests/test_redis/common.py 中实际为getenv('T_VERBOSE', 5)
T_MBUF以-m $T_MBUF启动 nutcracker,设置 mbuf 大小(字节)README 标注 default:521;tests/test_redis/common.py 中实际为getenv('T_MBUF', 512)
T_LARGE控制 mget/mset 大数量测试的 key 个数1000
T_LOGFILE测试日志输出目标(见下)默认log/t.log

以 README 为例:export T_VERBOSE=9将以-v 9启动 nutcracker;export T_MBUF=512将以-m 512启动;export T_LARGE=10000将让 mget/mset 用例压测 10000 个 key。注意 README 标注的默认值与common.py中getenv的缺省参数存在细微差异(4/521 vs 5/512),实际生效值以代码为准;各测试模块均通过int(getenv('T_VERBOSE', ...))统一读取,改动一处即全局生效。

T_LOGFILE的取值语义由 tests/lib/utils.py 的日志初始化逻辑实现:

  • export T_LOGFILE=-:日志打到 stderr(logging.basicConfig(level=DEBUG, ...),便于与 nosetests 输出混合观察);
  • export T_LOGFILE=t.log:日志写入当前目录t.log;
  • unset T_LOGFILE:回落默认值log/t.log(即tests/log/t.log),日志以 DEBUG 级别落盘,格式含时间戳、线程名与级别。

七、注意事项与故障排查

  1. core dump 属预期现象:全部集成测试跑完后可能出现 core dump——这是因为 tests/test_redis/test_basic.py 中的test_signal会主动向 nutcracker 发送 SIGSEGV(配合 tests/conf/control.sh 的ulimit -c unlimited),用于验证代理在崩溃信号下的行为与 core 生成。发现 core 文件不必惊慌,也无需视为测试失败。
  2. 残留进程导致测试失败:若测试失败,通常需要先清理残留进程再重跑:
pkill redis-server pkill redis-sentinel pkill nutcracker

这正是control.sh与teardown()无法覆盖的"上次崩溃遗留进程"场景;test_in_docker.sh每次在全新容器中运行,天然规避了该问题。 3.reload 类用例的版本门控:test_reload.py通过nc.version() < VERSION_SUPPORTING_RELOAD跳过不支持热加载的版本(当前标记为99.99.99,即事实上未启用),遇到"用例被跳过"的输出时请先核对代理版本。


八、C 单元测试:不依赖 Python 的底层验证

单元测试与集成测试相互独立,不依赖 Python,位于 src/test_all.c(共 607 行),可通过标准 autotools 流程编译运行:

make check

或按 README 说明直接查看失败输出:

cd src; make test_all; ./test_all

src/test_all.c 采用轻量的自研断言宏(expect_same_int、expect_same_uint32_t、expect_same_ptr),统计failures/successes计数。其覆盖重点包括:

  • 哈希算法正确性:对hash_one_at_a_time、hash_md5、hash_crc16、hash_crc32、hash_crc32a、hash_fnv1_32、hash_fnv1a_32、hash_fnv1_64、hash_fnv1a_64、hash_hsieh、hash_jenkins、hash_murmur等以"apple"为基准值断言——注释明确指出结果与 libmemcached 的tests/hash_results.h完全一致;ketama 一致性哈希则用"server1-8"的多个 index 断言;
  • 其他底层模块(配置解析等)的正确性验证。

在 Docker CI 中,对应步骤是docker run ... -c 'make test_all && ./test_all'(见 test_in_docker.sh),与集成测试串行执行、互为补充:先保证哈希与解析等纯 C 逻辑正确,再做跨进程的协议端到端验证。


结语

twemproxy 的测试体系是一个"C 单元测试 + Python 进程级集成测试 + Docker 一键化"的完整组合:make check守护底层算法正确性,nosetests 守护 Redis/Memcached 协议与代理行为的端到端正确性,而 test_in_docker.sh 将二者收敛为单条命令、可复现的 CI 流程。结合T_VERBOSE/T_MBUF/T_LARGE/T_LOGFILE四个环境变量,你无需改动任何代码即可调整测试的日志详细度、mbuf 大小与数据规模;参照 tests/lib/server_modules.py 的服务对象与@with_setup装饰器,新增一个覆盖新协议行为的用例只需复制改写一个文件。这套方法论同样适用于其他基于事件驱动的 C 代理项目的质量保障。

  • 后端

【免费下载链接】twemproxy

A fast, light-weight proxy for memcached and redis

项目地址:https://gitcode.com/gh_mirrors/twe/twemproxy
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询