App 测试绕不开"设备矩阵":Android 高低端 × 不同 ROM × iOS,单台机器上的设备池撑不住回归规模。2025 年把设备池从 5 台扩到 50 台,调度层完全重做,这篇讲设备农场的架构和几个绕不开的坑。
一、设备池的抽象:设备 = 资源,不是"手机"
第一原则:调度中心眼里只有"设备句柄",不关心它是 ADB 序列号还是 USB 口还是云真机。
1 |
|
capability 标签是调度的关键维度:用例声明自己需要哪些能力(多开用例要 multi_open,LBS 用例要 gps_mock),调度器做能力匹配。设备"是什么"(序列号)只出现在最底层的连接层,上层永远只见标签——这样加一批新设备,只要标签体系不变,调度零改动。
二、调度:三维匹配 + 亲和性
任务到设备的匹配规则,按优先级:
- 硬约束:平台 + capability 不匹配直接排除;
- 亲和性:同批次任务尽量落到同一设备/同一机架——减少"设备预热"和跨机架的 ADB 转发开销;
- 负载均衡:候选设备里选最近 1 小时利用率最低的;
- 故障规避:近 24h 有 fault 记录的设备降权(不是拉黑,fault 原因可能是偶发)。
1 | def pick_device(task, pool): |
为什么亲和性排第一:App 首次冷启动比热启动慢 3~5 秒,一批 50 个用例如果散落在 50 台设备上,光冷启动就多出 2~3 分钟,而且每台设备的 ROM 差异会把"环境问题"和"产品问题"混在一起。同批同设备(或同 ROM 设备)是失败归因的便宜保险。
三、设备健康:主动巡检,不是等用例挂
设备故障是无声的——屏幕黑了、触摸漂移了、ADB 断连了,都表现为"用例随机失败"。被动等失败归因太贵,建主动巡检:
1 | HEALTH_CHECKS = [ |
巡检结果进设备状态机:单项失败 → degraded(仍可接单但降权);连续 3 项或核心项(adb/touch)失败 → fault 自动摘除 + 工单。关键经验:degraded 状态比 fault 更有价值——80% 的设备问题(触摸漂移、响应变慢)在 degraded 期就能拦住,等 fault 时设备早就"看起来正常"地污染了一批用例。
四、50 台之后才暴露的问题
| 问题 | 症状 | 解法 |
|---|---|---|
| ADB 风暴 | 50 台并发 dump 窗口,USB 3.0 hub 全卡死 | dump 请求按机架分桶限流(每桶并发 ≤8),加 100ms 抖动 |
| 屏幕镜像带宽 | 实时投屏 50 路,管理网 1Gbps 打满 | 投屏默认关,按需开;开时限制 15fps/720p |
| 热堆积 | 机房设备连续跑 4h,CPU 降频,用例耗时 +40% | 批次间强制冷却 10min;机房风扇按温度联动 |
| 数据残留 | 上一任务登录态没清,下一任务"已登录"直接跑 | 任务结束钩子:强制卸载重装或清数据,由调度器执行不信任用例 |
五、小结
设备农场的三个层次:抽象(设备 = 带标签的句柄,新设备零代码接入)、调度(能力硬约束 + 亲和性 + 负载)、健康(主动巡检 + degraded 降级)。规模从 5 到 50 台,代码量翻了 3 倍,但用例层零改动——抽象做对了,扩容就是加标签的事。