3、集成测试环境搭建:Docker部署Redis集群、测试数据准备、测试框架选型(pytest+redis-py)

集成测试环境是错误码断言工程的基石。本章将完整搭建一套可复用的本地集成测试环境,涵盖Redis集群的Docker化部署、测试数据的结构化准备,以及pytest+redis-py测试框架的初始化配置。环境搭建遵循“容器化、可重复、易清理”原则,确保每次测试运行在一致的初始状态下。

3.1 Docker部署Redis集群

搜索服务的Redis集群采用3主3从架构,每个节点运行在独立的Docker容器中。使用docker-compose统一编排,确保网络互通与持久化目录隔离。

环境要求: Docker Engine ≥ 20.10,docker-compose ≥ 1.29,宿主机预留至少 6 个可用端口(7000-7005)。

3.1.1 集群配置文件

创建redis-cluster.yml,定义6个Redis节点服务:

version: '3.8'
services:
  redis-node-1:
    image: redis:7.0-alpine
    container_name: redis-node-1
    ports:
      - "7000:6379"
      - "17000:16379"
    volumes:
      - ./data/node-1:/data
      - ./conf/redis-cluster.conf:/usr/local/etc/redis/redis.conf
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-net

  redis-node-2:
    image: redis:7.0-alpine
    container_name: redis-node-2
    ports:
      - "7001:6379"
      - "17001:16379"
    volumes:
      - ./data/node-2:/data
      - ./conf/redis-cluster.conf:/usr/local/etc/redis/redis.conf
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-net

  # ... 节点3-6配置类似,端口依次为7002-7005 / 17002-17005

  redis-cluster-init:
    image: redis:7.0-alpine
    container_name: redis-cluster-init
    depends_on:
      - redis-node-1
      - redis-node-2
      - redis-node-3
      - redis-node-4
      - redis-node-5
      - redis-node-6
    command: >
      sh -c "sleep 5 && \
      redis-cli --cluster create \
      172.20.0.2:6379 172.20.0.3:6379 172.20.0.4:6379 \
      172.20.0.5:6379 172.20.0.6:6379 172.20.0.7:6379 \
      --cluster-replicas 1 --cluster-yes"
    networks:
      - redis-net

networks:
  redis-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

3.1.2 节点配置文件

每个节点共用同一份redis-cluster.conf,关键参数如下:

参数 值 说明
cluster-enabled yes 启用集群模式
cluster-config-file nodes.conf 集群状态持久化文件
cluster-node-timeout 5000 节点超时时间(ms)
appendonly yes 开启AOF持久化
protected-mode no 允许容器间跨节点访问
bind 0.0.0.0 监听所有网络接口

3.1.3 启动与验证

# 启动所有容器
docker-compose -f redis-cluster.yml up -d

# 等待集群初始化完成(约10秒)
sleep 10

# 验证集群状态
docker exec redis-node-1 redis-cli -p 6379 cluster info

# 预期输出包含 cluster_state:ok,且 cluster_size:3
验证要点: 使用 redis-cli --cluster check 127.0.0.1:7000 检查每个主节点是否分配了正确的哈希槽(0-5460、5461-10922、10923-16383),以及每个主节点是否挂载了对应的从节点。
Redis 集群 Docker 部署架构(3主3从) 宿主机(localhost) Docker 网络:redis-net(172.20.0.0/16) 主节点 1 172.20.0.2:6379 哈希槽:0-5460 端口映射:7000 主节点 2 172.20.0.3:6379 哈希槽:5461-10922 端口映射:7001 主节点 3 172.20.0.4:6379 哈希槽:10923-16383 端口映射:7002 从节点 1 172.20.0.5:6379 端口映射:7003 从节点 2 172.20.0.6:6379 端口映射:7004 从节点 3 172.20.0.7:6379 端口映射:7005 说明:主节点负责写操作,从节点同步复制;集群初始化容器(redis-cluster-init)在6个节点启动后自动分配哈希槽。

3.2 测试数据准备

测试数据分为三类:基础种子数据(模拟搜索索引)、错误码触发数据(用于断言验证)、边界数据(测试极限场景)。数据通过redis-py客户端在pytest的conftest.py中统一注入。

3.2.1 数据分类与结构

数据类别 Key 模式 Value 示例 用途
搜索索引 search:doc:{id} {"title":"Redis教程","content":"..."} 模拟已索引文档
错误码触发 test:error:{code} {"expected":"MOVED","slot":1234} 触发特定Redis错误码
边界数据 test:boundary:{type} "" 或 {"large":"x"*10240} 空值/超大值/特殊字符

3.2.2 数据准备脚本

在tests/conftest.py中定义seed_test_data函数,使用pytest的session作用域确保数据只加载一次:

import pytest
import redis
import json

@pytest.fixture(scope="session")
def redis_cluster():
    """创建Redis集群客户端连接"""
    startup_nodes = [
        {"host": "localhost", "port": 7000},
        {"host": "localhost", "port": 7001},
        {"host": "localhost", "port": 7002},
    ]
    client = redis.RedisCluster(
        startup_nodes=startup_nodes,
        decode_responses=True,
        skip_full_coverage_check=True
    )
    yield client
    # 清理:删除测试数据
    keys = client.keys("test:*")
    if keys:
        client.delete(*keys)

@pytest.fixture(scope="session", autouse=True)
def seed_test_data(redis_cluster):
    """注入测试种子数据"""
    # 1. 搜索索引数据(3条文档)
    docs = [
        {"id": "1001", "title": "Redis集群部署指南", "content": "步骤详解..."},
        {"id": "1002", "title": "错误码MOVED处理", "content": "重定向机制..."},
        {"id": "1003", "title": "pytest集成测试", "content": "断言技巧..."},
    ]
    for doc in docs:
        key = f"search:doc:{doc['id']}"
        redis_cluster.set(key, json.dumps(doc))

    # 2. 错误码触发数据
    error_cases = [
        {"code": "MOVED", "slot": 1234, "expected_node": "172.20.0.3:6379"},
        {"code": "ASK", "slot": 5678, "expected_node": "172.20.0.4:6379"},
        {"code": "CROSSSLOT", "keys": ["key1", "key2"]},
    ]
    for case in error_cases:
        key = f"test:error:{case['code']}"
        redis_cluster.set(key, json.dumps(case))

    # 3. 边界数据
    redis_cluster.set("test:boundary:empty", "")
    redis_cluster.set("test:boundary:large", "x" * 10240)
    redis_cluster.set("test:boundary:unicode", "中文测试🔍")

    return redis_cluster
注意: 集群模式下使用 keys * 可能触发跨槽扫描导致 CROSSSLOT 错误。建议使用 SCAN 命令或按已知前缀逐个删除。上述脚本中 keys("test:*") 在集群环境下可能不完整,生产级清理应改用 SCAN 迭代。

3.3 测试框架选型:pytest + redis-py

选择pytest作为测试框架,redis-py作为Redis客户端,二者组合具备以下优势:

  • pytest:支持fixture、参数化、插件生态(pytest-xdist并行、pytest-assume软断言)
  • redis-py:原生支持RedisCluster,自动处理MOVED/ASK重定向,提供RedisClusterException异常类
  • 集成优势:fixture管理连接生命周期,参数化覆盖多种错误码场景,断言直接捕获异常码

3.3.1 依赖安装

# requirements-dev.txt
pytest==7.4.0
redis==5.0.0
pytest-xdist==3.3.1   # 可选:并行测试
pytest-assume==2.4.3  # 可选:软断言

3.3.2 测试框架初始化结构

项目测试目录结构如下:

tests/
├── conftest.py              # 全局fixture:redis_cluster, seed_test_data
├── test_error_code_assert/  # 错误码断言测试模块
│   ├── __init__.py
│   ├── test_moved_assert.py
│   ├── test_ask_assert.py
│   └── test_crossslot_assert.py
├── test_data_preparation/   # 数据准备验证模块
│   ├── __init__.py
│   └── test_seed_data.py
└── pytest.ini               # pytest配置文件

pytest.ini 配置示例:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
    error_code: 错误码断言测试
    data_prep: 数据准备验证
    slow: 耗时较长的测试
log_cli = true
log_cli_level = INFO

3.3.3 第一个集成测试用例

验证种子数据是否成功注入:

# tests/test_data_preparation/test_seed_data.py
import json
import pytest

class TestSeedData:
    """验证测试数据准备是否正确"""

    def test_search_doc_exists(self, redis_cluster):
        """搜索索引数据应可正常读取"""
        doc_json = redis_cluster.get("search:doc:1001")
        assert doc_json is not None, "文档1001未找到"
        doc = json.loads(doc_json)
        assert doc["title"] == "Redis集群部署指南"

    def test_error_trigger_data(self, redis_cluster):
        """错误码触发数据应包含expected字段"""
        error_json = redis_cluster.get("test:error:MOVED")
        error_data = json.loads(error_json)
        assert "expected_node" in error_data
        assert error_data["expected_node"] == "172.20.0.3:6379"

    def test_boundary_empty_value(self, redis_cluster):
        """空值边界数据应返回空字符串"""
        value = redis_cluster.get("test:boundary:empty")
        assert value == ""

    @pytest.mark.slow
    def test_boundary_large_value(self, redis_cluster):
        """大值边界数据应完整返回"""
        value = redis_cluster.get("test:boundary:large")
        assert len(value) == 10240
pytest + redis-py 集成测试数据流 pytest 测试框架 conftest.py ├─ redis_cluster fixture ├─ seed_test_data fixture └─ 测试用例(test_*.py) redis-py 客户端 RedisCluster ├─ 自动槽路由 ├─ MOVED/ASK 处理 └─ 异常映射 Redis 集群 3主3从 ├─ 搜索索引数据 ├─ 错误码触发数据 └─ 边界数据 fixture 注入 命令执行 测试数据准备流程(session 作用域) 1. 连接集群 2. 注入索引数据 3. 注入错误码数据 4. 注入边界数据 测试结束后,teardown 自动清理 test:* 前缀的所有键

3.4 环境验证清单

完成上述步骤后,执行以下验证命令确保环境就绪:

# 1. 验证Docker容器运行状态
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep redis

# 2. 验证集群状态
docker exec redis-node-1 redis-cli -p 6379 cluster nodes | head -10

# 3. 运行数据准备验证测试
pytest tests/test_data_preparation/ -v

# 4. 运行空测试确认框架正常
pytest --collect-only
快速排障: 如果 redis.RedisCluster 连接报 MaxConnectionError,检查Docker端口映射是否与 startup_nodes 中的端口一致;如果出现 ClusterDownError,等待集群初始化完成(约10秒)或检查 redis-cluster-init 容器日志。