弱网测试怎么做:用 mitmproxy 模拟真实网络劣化

用户反馈里最经典的一类:“为什么你们测试都过了,我这边 App 一直转圈?”——答案通常是没人测过弱网。这篇文章介绍一套基于 mitmproxy 的弱网模拟方案:不依赖真机、可脚本化、可进 CI。

一、为什么用 mitmproxy 而不是系统级工具

方案 优点 缺点
系统工具(Clumsy/ATC) 傻瓜式 绑死 GUI,没法进 CI,参数难精确
交换机 QoS 真实 成本高、粒度粗、要机房配合
mitmproxy add-on 可编程、参数精确、CI 友好 只覆盖 TCP 层可控部分,DNS/RTT 要配合

核心思路:设备流量经过 mitmproxy 代理,用 add-on 在请求/响应路径上插入可控的延迟、限速、丢包,全部用 YAML 声明场景,脚本按场景启动。

二、弱网场景模型

把"弱网"拆成四个独立维度,每个维度单独可调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/proxy/weaknet_scenarios.yaml
wifi_bad: # 电梯/地铁 WiFi
latency_ms: 300 # 单程延迟
bandwidth_kbps: 500
loss_pct: 2 # 丢包率
cell_3g: # 3G 时代体验
latency_ms: 800
bandwidth_kbps: 2000
loss_pct: 5
flaky: # 时断时续(最难测,也最有价值)
latency_ms: 200
bandwidth_kbps: 1000
loss_pct: 15
stall_every: 8 # 每 8 个请求卡住一次
stall_ms: 5000

经验值flaky(间歇性中断)场景抓到的 bug 数量是稳定弱网的 3 倍以上——用户手机的实际网络就是"时好时坏",而不是"稳定很慢"。

三、add-on 实现核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# rpa_tools/proxy_manager.py(节选)
class WeaknetAddon:
def __init__(self, scenario: dict):
self.scenario = scenario
self.counter = 0

def request(self, flow):
s = self.scenario
# 1) 延迟:在请求侧注入(模拟 RTT 的一半)
time.sleep(s["latency_ms"] / 1000)
# 2) 丢包:按概率直接掐断
if random.random() * 100 < s.get("loss_pct", 0):
flow.error = http.Response(408, "Timeout", b"")
# 3) 间歇卡顿
self.counter += 1
if s.get("stall_every") and self.counter % s["stall_every"] == 0:
time.sleep(s.get("stall_ms", 0) / 1000)

def responseheaders(self, flow):
# 4) 限速:按带宽把响应切成小片慢慢吐
self._throttle(flow)

限速不是 time.sleep 一刀切,而是按 chunk 发送bandwidth_kbps 换算成每 KB 的间隔,响应流式分片释放,这样前端能观察到"转圈越来越长"的真实过程,而不是一开始就等很久。

四、和 UI 自动化联动

弱网不是"代理自己跑",而是给 UI 用例套一层场景

1
2
3
4
5
6
7
8
9
10
11
12
13
@pytest.fixture(scope="session")
def weaknet():
mgr = ProxyManager()
yield mgr # mgr.apply("flaky") / mgr.reset()
mgr.stop()

@pytest.mark.weaknet("flaky")
def test_login_under_flaky_network(driver, weaknet):
weaknet.apply("flaky")
open_login_page(driver)
submit_login(driver)
# 关键断言:允许失败重试,但必须有 loading 态 + 明确的失败文案
assert wait_loading_or_error(driver, timeout=30)

pytest.ini 里注册 weaknet 标记,CI 的 api 档流水线里加一条 pytest -m weaknet 的 job,每个发版前自动过一遍全部弱网场景

五、断言什么才算"弱网表现合格"

弱网下的测试重点不是功能能不能完成(那是正常网络的测试职责),而是:

  1. 有 loading 反馈:任何超过 1s 的操作必须有可见的等待指示;
  2. 失败有出口:超时/断网必须有重试按钮或明确提示,禁止白屏死等;
  3. 幂等:网络抖动导致重复提交时,服务端不能创建两条订单——这条必须用接口层断言兜住;
  4. 恢复即续:网络恢复后,之前"卡住"的页面状态要能自然续上,而不是整页白屏要求重进。

六、小结

弱网测试的落地三件套:场景 YAML 化、add-on 可编程、标记进 CI。工具选型上 mitmproxy 的 Python add-on 生态让"网络条件"变成了和普通测试数据一样可以参数化的东西——这是它比 GUI 工具强的根本原因。