2. 集成测试环境搭建:基于Docker的离线代理、模拟网络限速工具、本地存储模拟器

2.1 原理概述

离线优先应用的集成测试需要模拟真实设备在无网络或弱网络环境下的行为。本章环境搭建的核心目标是:

  • 离线代理:在容器内构建一个可拦截并阻断外部请求的HTTP/HTTPS代理,使被测应用认为网络已断开。
  • 网络限速工具:通过流量整形工具(如 tc / netem)模拟低带宽、高延迟、丢包等网络状况。
  • 本地存储模拟器:使用轻量级文件系统或对象存储模拟器(如MinIO、Caddy)缓存离线数据,验证本地读写与同步逻辑。

三者通过 Docker Compose 编排成一体,让集成测试在隔离且可重复的环境中运行。

设计原则:所有网络控制与存储服务均运行在独立容器中,被测应用仅访问代理与本地存储,不直接连接公网。

2.2 架构组件一览

组件 容器镜像 作用 暴露端口
离线代理 mitmproxy/mitmproxy 拦截所有HTTP(S)请求,可配置返回离线响应或阻断连接 8080 (HTTP), 8081 (Web GUI)
网络限速器 alpine:3.19 + tc 通过 netem 对代理出口带宽做延迟/丢包/限速 无
本地存储模拟器 minio/minio 模拟S3兼容对象存储,本地缓存文件 9000 (API), 9001 (Console)

2.3 Docker Compose 编排实现

以下 docker-compose.yml 定义三个服务,网络拓扑为先经过限速器再到离线代理,最后到达应用。

version: "3.9"
services:
  offline-proxy:
    image: mitmproxy/mitmproxy:10.4.2
    command: ["mitmweb", "--listen-port", "8080", "--web-port", "8081", "--allow-hosts", ".*"]
    ports:
      - "8080:8080"
      - "8081:8081"
    volumes:
      - ./mitmproxy-ca:/home/mitmproxy/.mitmproxy  # 持久化CA证书
    networks:
      - test-net
    depends_on:
      - network-shaper

  network-shaper:
    image: alpine:3.19
    cap_add:
      - NET_ADMIN
    command: ["sh", "-c",
      "apk add --no-cache iproute2 iperf3 && \
       tc qdisc add dev eth0 root netem delay 200ms 50ms loss 10% rate 50kbps && \
       tail -f /dev/null"]
    networks:
      - test-net

  minio:
    image: minio/minio:RELEASE.2025-03-12T00-00-00Z
    command: ["server", "/data", "--console-address", ":9001"]
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: testadmin
      MINIO_ROOT_PASSWORD: testpass123
    volumes:
      - minio-data:/data
    networks:
      - test-net

volumes:
  minio-data:

networks:
  test-net:
    driver: bridge

2.4 网络限速器详解

限速容器使用 iproute2 中的 tc qdisc 与 netem 模块。常用参数:

参数 示例 说明
delay 200ms 50ms 固定延迟200ms,抖动±50ms
loss 10% 随机丢包率10%
rate 50kbps 限制带宽为50kbps(约6.25KB/s)
duplicate 1% 重复包比例1%

测试时可通过 iperf3 验证速度:

# 在限速容器内部启动服务器
iperf3 -s &

# 在另一个容器(如offline-proxy)作为客户端
iperf3 -c network-shaper -t 10

2.5 离线代理配置(mitmproxy)

离线代理需要在收到请求后直接返回 503 Service Unavailable 或者提供一个预录制的离线响应。通过 mitmproxy 的 addons 实现:

# offline_addon.py
from mitmproxy import http

class OfflineMode:
    def request(self, flow: http.HTTPFlow) -> None:
        if flow.request.pretty_host == "api.example.com":
            # 模拟离线:返回503并阻断
            flow.response = http.Response.make(
                503,
                b'{"error":"offline"}',
                {"Content-Type": "application/json"}
            )
        elif flow.request.pretty_host == "cdn.example.net":
            # 模拟本地缓存替换:返回本地MinIO地址
            flow.response = http.Response.make(
                200,
                b'{"redirect":"http://minio:9000/cached-file.png"}',
                {"Content-Type": "application/json"}
            )

addons = [OfflineMode()]

挂载方式:

volumes:
  - ./offline_addon.py:/home/mitmproxy/offline_addon.py
command: ["mitmweb", "--listen-port", "8080", "--web-port", "8081",
          "--set", "block_global=false", "-s", "/home/mitmproxy/offline_addon.py"]

2.6 本地存储模拟器(MinIO)初始化

创建Bucket并设置离线缓存策略:

# 通过mc客户端(MinIO Client)初始化
docker run --rm --network test-net \
  -e "MINIO_ROOT_USER=testadmin" \
  -e "MINIO_ROOT_PASSWORD=testpass123" \
  minio/mc:latest \
  sh -c "
    mc alias set local http://minio:9000 testadmin testpass123;
    mc mb local/offline-cache;
    mc policy set download local/offline-cache;
    mc event add local/offline-cache arn:minio:sqs:::* --event put;
  "

2.7 调试与验证

  1. 检查容器网络连通性:
docker compose exec offline-proxy ping -c 2 network-shaper
docker compose exec minio curl -s http://minio:9000/minio/health/live
  1. 验证限速效果:从app容器向代理发送请求,观察延迟与成功率:
docker compose run --rm app curl -x http://offline-proxy:8080 http://httpbin.org/get -o /dev/null -w "%{time_total}s %{http_code}"
  1. 查看mitmproxy Web界面:浏览器访问 http://localhost:8081 查看实时流量。正确配置后应看到请求被离线插件拦截。

2.8 部署到CI/CD

在GitLab CI或GitHub Actions中启动环境:

# .gitlab-ci.yml 片段
integration-test:
  services:
    - docker:dind
  script:
    - docker compose up -d
    - sleep 5  # 等待限速器初始化
    - ./run-tests.sh
    - docker compose down

注意:CI环境需要支持 NET_ADMIN 权限,可在容器启动时添加 --cap-add=NET_ADMIN 或者设置 privileged: true。

2.9 常见错误与解决方法

错误现象 原因 解决方式
限速不生效 tc qdisc 被host网络覆盖 确保容器使用bridge网络,且具有NET_ADMIN权限
mitmproxy证书不被信任 应用未安装CA证书 将容器内 /home/mitmproxy/.mitmproxy/mitmproxy-ca-cert.pem 导出并安装到被测应用容器
MinIO连接超时 网络限速器影响内部通信 将MinIO与限速器放在同一个网络但不在限速器后面;或为MinIO单独开一个网络
docker compose up 时限速容器退出 tail -f /dev/null 被替换 使用 tini 或 sleep infinity 作为持久命令

2.10 性能与稳定性优化

  • 复用CA证书:将 ./mitmproxy-ca 目录加入版本控制,避免每次部署重新生成。
  • 限速参数化:通过环境变量注入 DELAY、LOSS、RATE,便于不同测试场景切换。
  • MinIO数据持久化:使用命名卷(如 minio-data)可保留缓存数据跨重启。
  • 清理qdisc:每次启动限速容器时先清除旧规则:
command: ["sh", "-c",
  "apk add --no-cache iproute2 && \
   tc qdisc del dev eth0 root 2>/dev/null; \
   tc qdisc add dev eth0 root netem delay ${DELAY:-200ms} loss ${LOSS:-10%} rate ${RATE:-50kbps}; \
   tail -f /dev/null"]
最佳实践:在集成测试脚本中,每次测试前重置限速器(重启容器或执行 tc qdisc change),确保状态隔离。
集成测试环境网络拓扑 集成测试环境网络拓扑 被测应用 网络限速器 tc netem 离线代理 mitmproxy MinIO 外部网络 流程说明: 1. 应用通过限速器发送请求 2. 限速器模拟延迟/丢包 3. 代理拦截并返回离线响应 限速示例参数: 延迟 200ms ±50ms 丢包率 10% 带宽 50kbps MinIO 本地缓存 Bucket: offline-cache 与代理配合缓存离线文件

2.11 集成测试验证要点

  • 应用启动后应自动发现本地MinIO并连接,不抛出网络异常。
  • 网络限速器生效后,应用请求超时应正常触发离线缓存回退。
  • 离线代理返回503时,应用应展示本地缓存内容并记录同步失败日志。
  • 模拟恢复网络(停止限速器或调整tc规则)后,应用应自动触发后台同步。
注意:在CI/CD环境中请勿将privileged: true用于生产,建议使用专为Docker优化的--cap-add=NET_ADMIN并限制其它能力。