不止于抓包:用Mitmproxy + Python脚本实现iOS/Android App自动化测试数据Mock
在移动应用开发的生命周期中,自动化测试是确保产品质量的关键环节。然而,传统的测试方法往往受限于后端服务的稳定性和多样性,难以模拟复杂的网络环境和异常数据场景。这正是Mitmproxy结合Python脚本大显身手的领域——它不仅是一个抓包工具,更是自动化测试中数据Mock的瑞士军刀。
想象一下这样的场景:你需要测试App在网络延迟时的表现,或者验证它对异常API响应的容错能力。传统方法可能需要修改后端代码或搭建复杂的测试环境,而Mitmproxy+Python的方案则可以在不修改任何生产代码的情况下,动态拦截和修改网络请求,实现各种测试场景的快速模拟。本文将带你深入这一技术组合的高级应用,从原理到实践,构建一个完整的Mock服务解决方案。
1. Mitmproxy在自动化测试中的核心优势
Mitmproxy之所以成为自动化测试的利器,源于其独特的架构设计和工作原理。与常见的抓包工具不同,Mitmproxy是一个基于Python开发的中间人代理,提供了完整的API用于编程式地操控网络流量。
三大核心能力使其在测试领域脱颖而出:
- 实时流量拦截与修改:可以在请求到达服务器前或响应返回客户端前进行动态修改
- 脚本化控制:通过Python脚本实现复杂的流量处理逻辑
- 多协议支持:不仅支持HTTP/HTTPS,还能处理WebSocket等现代协议
对比其他方案,Mitmproxy的最大优势在于其灵活性。例如,传统的Mock服务器需要预先定义所有可能的响应,而Mitmproxy可以根据运行时条件动态生成响应。这在需要模拟大量测试用例或复杂业务逻辑时尤其有价值。
提示:Mitmproxy的透明代理模式特别适合移动端测试,因为它不需要修改App代码或配置,只需设置设备网络即可生效。
2. 环境搭建与基础配置
2.1 安装与基本配置
在Mac上安装Mitmproxy非常简单,推荐使用Homebrew:
brew install mitmproxy对于Python脚本支持,建议创建一个虚拟环境:
python -m venv mitm-env source mitm-env/bin/activate pip install mitmproxy2.2 透明代理与正向代理的选择
在移动测试场景中,我们通常面临两种代理模式选择:
| 特性 | 透明代理 | 正向代理 |
|---|---|---|
| 设备配置 | 需修改网络设置 | 需在App或系统中配置代理 |
| 适用场景 | 无法修改代理设置的App | 可控制代理配置的环境 |
| 复杂度 | 初始配置复杂 | 配置简单 |
| 兼容性 | 可能遇到证书问题 | 证书管理更直观 |
对于iOS/Android设备测试,透明代理通常是更好的选择,因为它不需要修改App本身。以下是配置透明代理的关键步骤:
# 启用IP转发 sudo sysctl -w net.inet.ip.forwarding=1 # 设置端口转发规则 echo "rdr pass on en0 inet proto tcp to any port {80,443} -> 127.0.0.1 port 8080" | sudo pfctl -ef - # 启动Mitmproxy透明模式 mitmdump --mode transparent -s mock_script.py3. Python脚本开发:从基础到高级
3.1 基本请求拦截
创建一个基础的Mock脚本(mock_script.py)来拦截和修改请求:
from mitmproxy import http def request(flow: http.HTTPFlow) -> None: # 拦截特定API请求 if "api.example.com/data" in flow.request.url: # 直接返回Mock数据,不发送到真实服务器 flow.response = http.Response.make( 200, b'{"status":"success","data":"mocked"}', {"Content-Type": "application/json"} )这个简单示例展示了Mitmproxy脚本的基本结构。当检测到目标API请求时,它会直接返回预设的JSON响应,完全绕过真实服务器。
3.2 动态响应生成
更高级的用法是根据请求内容动态生成响应:
def response(flow: http.HTTPFlow) -> None: if "api.example.com/user" in flow.request.url: # 解析请求参数 user_id = flow.request.query.get("id", ["1"])[0] # 生成动态响应 flow.response = http.Response.make( 200, f'{{"id":{user_id},"name":"User {user_id}"}}'.encode(), {"Content-Type": "application/json"} )3.3 异常场景模拟
自动化测试中经常需要模拟各种异常情况:
import random def response(flow: http.HTTPFlow) -> None: # 随机模拟服务器错误 if random.random() < 0.3: # 30%概率触发错误 flow.response = http.Response.make( 500, b'{"error":"Internal Server Error"}', {"Content-Type": "application/json"} ) # 模拟网络延迟 if "api.example.com/slow" in flow.request.url: import time time.sleep(3) # 3秒延迟4. 实战:构建完整的Mock测试系统
4.1 A/B测试接口模拟
现代App经常使用A/B测试来优化用户体验。我们可以用Mitmproxy轻松模拟不同的测试分组:
AB_TEST_CASES = { "feature_new_ui": { "group_a": {"enabled": True, "color_scheme": "dark"}, "group_b": {"enabled": False, "color_scheme": "light"} } } def response(flow: http.HTTPFlow) -> None: if "api.example.com/ab_test" in flow.request.url: user_id = hash(flow.client_conn.ip_address) % 2 # 根据IP简单分组 group = "group_a" if user_id == 0 else "group_b" flow.response = http.Response.make( 200, json.dumps(AB_TEST_CASES["feature_new_ui"][group]).encode(), {"Content-Type": "application/json"} )4.2 自动化测试集成
将Mitmproxy Mock服务集成到自动化测试流程中:
import subprocess import time import pytest @pytest.fixture(scope="module") def start_mock_server(): # 启动Mitmproxy进程 process = subprocess.Popen([ "mitmdump", "--mode", "transparent", "-s", "mock_script.py", "--set", "listen_port=8080" ]) time.sleep(2) # 等待服务器启动 yield process.terminate() def test_app_with_mock(start_mock_server): # 这里运行实际的App测试代码 # 所有网络请求都会被mock_script.py处理 pass4.3 性能测试场景
模拟高延迟或低带宽环境:
def response(flow: http.HTTPFlow) -> None: # 为大文件添加延迟 if flow.response and flow.response.content: content_size = len(flow.response.content) if content_size > 1024*1024: # 大于1MB的文件 import time time.sleep(content_size / (1024*50)) # 模拟50KB/s的网速5. 高级技巧与最佳实践
5.1 状态管理
有时我们需要在多个请求间保持状态:
from mitmproxy import ctx class StateManager: def __init__(self): self.user_sessions = {} def request(self, flow: http.HTTPFlow) -> None: if "login" in flow.request.url: # 存储登录状态 session_id = flow.request.headers.get("Session-ID") if session_id: self.user_sessions[session_id] = { "logged_in": True, "last_active": time.time() } addons = [StateManager()]5.2 流量录制与回放
实现简单的流量录制功能:
class Recorder: def __init__(self): self.flows = [] def response(self, flow: http.HTTPFlow) -> None: self.flows.append({ "url": flow.request.url, "method": flow.request.method, "request": flow.request.text, "response": flow.response.text }) def save(self, filename): import json with open(filename, "w") as f: json.dump(self.flows, f) recorder = Recorder() def done(): recorder.save("recorded_traffic.json")5.3 安全注意事项
在使用Mitmproxy进行测试时,需要注意:
- 证书管理:确保测试设备正确安装了Mitmproxy的CA证书
- 敏感数据:避免在生产环境使用,防止敏感信息泄露
- 性能影响:复杂的脚本处理可能引入性能开销
# 示例:自动拒绝包含敏感信息的请求 SENSITIVE_KEYWORDS = ["password", "credit_card", "ssn"] def request(flow: http.HTTPFlow) -> None: if any(keyword in flow.request.url for keyword in SENSITIVE_KEYWORDS): flow.response = http.Response.make( 403, b'{"error":"Sensitive data not allowed in test environment"}', {"Content-Type": "application/json"} )在实际项目中,我发现最有效的Mock策略是根据测试用例的需求分层设计脚本:基础层处理通用的网络异常模拟,业务层针对特定功能定制响应逻辑。这种分层方法既保证了灵活性,又避免了脚本过于复杂难以维护。