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 调试与验证
- 检查容器网络连通性:
docker compose exec offline-proxy ping -c 2 network-shaper
docker compose exec minio curl -s http://minio:9000/minio/health/live
- 验证限速效果:从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}"
- 查看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),确保状态隔离。
2.11 集成测试验证要点
- 应用启动后应自动发现本地MinIO并连接,不抛出网络异常。
- 网络限速器生效后,应用请求超时应正常触发离线缓存回退。
- 离线代理返回503时,应用应展示本地缓存内容并记录同步失败日志。
- 模拟恢复网络(停止限速器或调整
tc规则)后,应用应自动触发后台同步。
注意:在CI/CD环境中请勿将
privileged: true用于生产,建议使用专为Docker优化的--cap-add=NET_ADMIN并限制其它能力。