安卓自动化测试平台AutoGod:从设备调度到稳定性调优完整实践 做安卓自动化测试平台这事我是被逼出来的。前两年团队一直用Appium和UiAutomator2写回归脚本功能都能跑但一到大版本回归就露馅几十台真机同时开跑脚本写的人各管一摊今天缺个依赖明天设备掉线没人管凌晨三点守着告警群看“失败原因找不到元素”看到怀疑人生。后来我实在坐不住了把手上那套“脚本堆”拆掉重做从设备管理、任务调度、元素识别到报告聚合全部平台化名字就叫AutoGod。现在团队将近40台安卓设备接入回归测试从原来的“人力盯跑”变成“自动调度按需介入”稳定性从70%出头提到了95%以上。这篇就是整个平台从设计到落地到调优的完整复盘内容会比较长但每一步都值得参考。1. 为什么我要自研一套安卓自动化测试平台我知道很多人听到“自研”两个字就头大觉得团队小、没资源不如直接用开源方案。这个心态没错但问题在于开源框架解决的是“怎么写脚本”的问题而测试团队大多数时间其实耗在“脚本能不能稳定跑完”这件事上。AutoGod的诞生本质上就是把后面这件事从脚本逻辑里剥离出来交给平台层去处理。1.1 Appium和UiAutomator2到底哪里不够先说Appium。它是个好东西跨平台、生态成熟但它在安卓真机大规模并发场景下的问题也很明显。每个session都要重新启动UiAutomator2 server驱动初始化大概要花3到5秒如果同时跑20台设备光初始化时间和adb端口冲突就够喝一壶。更麻烦的是Appium的session和WebDriver协议绑定得很死一旦中间连接闪断整个用例直接挂掉你还得写一堆重试逻辑去保护它。UiAutomator2单独用的话性能会好一些但它本质上还是一个测试框架不提供设备管理、任务调度、结果聚合这些运维能力。你可以用它写单条用例却很难靠它管理一个“真机池”。我们的实践是框架负责执行动作平台负责调度和保障两者职责分开一切才开始顺起来。还有一个隐形成本是脚本质量。不同的人写出来的脚本风格差异极大有人喜欢把等待时间写死成sleep(5)有人习惯用坐标点控件有人完全不处理弹窗。这些脚本单独跑都行合在一起跑一个完整业务流时就各种互相干扰。平台化之后我们强制用例走统一的动作原语和等待策略脚本风格不再是短板。1.2 AutoGod的定位平台而非框架我理解的“平台”和“框架”有个本质区别框架是你调用它平台是它调配你。AutoGod设计的核心思路是把测试场景里那些容易让人类崩溃的重复性工作——连接设备、分配任务、收集失败证据、生成报告——全部下沉到平台层。比如我们早期跑回归最痛苦的一环是“这台设备现在在干嘛”。没人说得清。有人手动跑了用例有人拿它连了Android Studio测试平台又以为它是空闲的。等回归任务一分配冲突立刻爆发要么串台要么卡死。AutoGod的初始版本最优先做得就是设备状态的统一管理每个设备在平台里只有一个状态谁在用、被哪个任务占用、还剩多少电量一屏都能看到。这个定位也决定了开发量级。第一版我们没花时间去做复杂的图形化编辑器先把“设备接入、任务调度、用例执行、报告输出”这条链路打通。任何一个环节没打通自动化跑得再漂亮也落不了地。我当时的底线是哪怕UI丑一点但任务必须能按预期跑完失败必须能定位到原因这条底线后来证明了是对的。2. 设备接入层的设计先让40台真机乖乖排队设备层是整个平台的地基。手机连不上、状态不稳定后面所有功能都等于空中楼阁。AutoGod的设备接入层我们前后重构了三次这里只说最终沉淀下来的方案。2.1 设备状态机与adb连接保活我们定义了一套设备状态机每个设备在任意时刻只处于下面五种状态之一状态含义允许进入的条件offline设备掉线或初始化失败adb devices中显示offline或心跳超时idle在线但未被任务占用平台启动时或任务执行完毕释放ready已通过能力检测可分配任务设备在线、电量/存储/系统版本符合任务要求busy正在执行测试任务任务调度器分配设备时自动置位degraded在线但处于亚健康状态内存不足、连续执行失败、USB带宽异常这五个状态不是画着好看的每条状态转换都有对应的动作。比如设备从offline变回在线平台会自动做一次“健康检查”adb shell getprop读取系统版本wm size读取分辨率df读取存储空间然后跑一个5秒钟的冒烟用例通过之后才置为ready。adb连接保活是另一个容易翻车的点。USB连接本身就不可靠插拔、休眠、供电波动都会导致设备直接offline。我们做了一套周期心跳机制每30秒对每台设备执行一次adb shell echo ok连续3次无响应就标记offline并尝试adb reconnect恢复。这里的经验是千万不要等用例跑挂了才发现设备掉了心跳机制的成本很低但对稳定性提升非常明显。2.2 基于能力画像的调度策略设备不能随便分配。有的任务要求Android 12以上有的任务要求屏幕分辨率至少1080p有的任务对硬件性能敏感不能在低端设备上跑。所以AutoGod在设备初始接入时就会为每台设备生成一份“能力画像”包含系统版本、分辨率、CPU架构、内存大小、剩余存储、电池健康度等字段。任务创建时可以声明要求调度器只把任务分配给能力匹配的设备。调度权重也是实际踩坑踩出来的。一开始我们简单按“空闲设备随机分配”结果就是低端机频繁被分到大任务跑一个case要20分钟其他设备闲着。后来改成分数制每台设备有一个综合评分执行速度和历史稳定性越高越容易被优先分配同时引入“惩罚分”如果某台设备连续失败超过3次临时降低它的分配优先级把任务挪到更可靠的设备上。2.3 执行节点与脚本仓库的解耦早期脚本是放在执行节点本地的节点挂了脚本就没了换台设备还得重新部署。AutoGod把脚本仓库做成了独立服务执行节点只负责拉取和执行。具体流程是调度中心下发任务时附带脚本ID节点收到后从仓库拉取对应版本的脚本到本地缓存然后执行。执行完毕把结果上报缓存只保留最近10个版本。这个设计带来一个直接好处脚本发布和测试执行可以完全异步。白天开发改完脚本推到仓库晚上定时任务自动拉取新版本跑回归不需要人手动去每台机器上更新。我们在仓库里还做了版本校验确保节点只要脚本ID变就重新拉取避免旧的本地缓存影响结果。3. 元素识别与等待策略把“找不到控件”消灭在系统之外做安卓自动化最常听到的报错就是“no such element”。这句话背后的原因千奇百怪页面还没加载完、控件是动态列表里的项、App用了Flutter/自绘引擎导致属性树拿不到、甚至仅仅是动画还没结束。AutoGod的元素识别层目标就是把这些原因一次性兜住。3.1 三层识别兜底属性树、图像、OCR我们做了一套三层识别引擎按优先级从高到低依次尝试第一层属性树定位。通过UiAutomator2或AccessibilityService拉取当前界面的控件树用resource-id、text、content-desc等属性组合定位目标元素。这是最高效的方式速度在200毫秒以内。第二层图像定位。控件树拿不到或者拿不到完整信息时截屏后用OpenCV做模板匹配或特征匹配。Flutter等自绘UI尤其受用因为控件树里经常只有一个根节点。模板图可以预先在平台上传也可以从历史截图里自动裁剪生成。第三层OCR定位。图像匹配也失败时调用OCR引擎识别屏幕文本再通过文本位置反推元素坐标。PaddleOCR的识别速度和质量都还不错我们最终选型用的就是它。这套策略的兜底顺序是刻意设计的属性树最精确但覆盖有限图像定位覆盖广但怕画面变化OCR最笨但只需要“看见文字”。三层都失败才判定为找不到元素这个失败率被我们压到了极低。3.2 动态等待从sleep到预期状态收敛很多人一写等待就sleep(5)粗暴但有效代价是每次执行都白白浪费几秒几十台设备几十个用例叠加起来速度慢得吓人。AutoGod统一用“动态等待”替代睡死每次操作前轮询检查目标元素是否满足“可见且可点击”这个预期状态轮询间隔500毫秒最大超时15秒。只要状态满足就立刻继续不满足就等到超时。启动App的等待比较特殊我们给了一个独立的30秒超时因为冷启动时首帧渲染、网络请求、广告弹窗都可能导致页面迟迟不稳定。这里还有一个细节不是等到元素出现就立刻点击而是额外等待“页面静止”信号即连续两次截图内容完全一致。实践证明这个“静止判定”能把误点击率降下来一大截尤其是页面有入场动画的时候。3.3 自愈与自动重试的边界有了这些识别策略还是会碰到极端情况。AutoGod做了一个自愈机制当元素定位失败时自动截取当前屏幕图重新执行一次页面结构解析如果发现当前页面有全局弹窗或键盘遮挡先做一次关闭弹窗/收起键盘的操作然后重新定位。这个自愈逻辑只执行一次不搞无限循环免得把问题掩盖掉。自动重试也是需要的但边界必须清晰。我们在平台层默认只对“环境类失败”自动重试比如连接超时、设备无响应、系统UI崩溃这类失败重试一次成功率很高。业务断言失败、元素最终超时这种“业务类失败”不重试直接上报因为重试也大概率还是失败反而浪费时间。这个区分非常关键否则你会看到一份99%通过率的报告实际上一半用例都是“重试才通过的”。4. 用例编排与数据构建真正决定测试效率的地方设备管理和元素识别做得再完善如果用例编排乱糟糟整个平台还是跑不出效率。AutoGod把用例抽象成三层步骤、场景、套件同时把测试数据和用例体分离这套建模我们受益良多。4.1 分层用例模型用例、场景、套件“步骤”是最小执行单元我们定义了一批动作原语点击、输入、滑动、截图、等待元素出现、断言文本存在、切换WebView一共就这么几个。所有复杂操作都由这些原语组合而成不允许在用例里直接写私有逻辑。“场景”对应一条完整的业务流比如“登录→首页浏览→加入购物车→支付成功”一个场景由若干步骤按照顺序编排组成。场景可以在不同设备上并行、可以在不同测试账号下反复执行这是AutoGod调度的最小分配单位。“套件”则是场景的集合对应一次完整的回归计划。一个套件可以声明依赖的设备数量、期望总耗时、失败即停还是继续执行。这样产品验收时不再是“跑一下回归看看”而是直接触发一个定义了明确范围的套件跑完自动出报告。这种分层的最大好处是观察性。当某个场景失败平台可以精确告诉你失败发生在第几个步骤步骤执行的输入参数是什么当时的截图和日志分别是什么。排查问题的时间从“小时级”降到了“分钟级”。4.2 数据准备的工程化隔离、生成和清理自动化测试里数据问题比脚本问题更隐蔽。你创建了一个订单下次再跑同样的流程系统提示“订单号重复”脚本就挂了。AutoGod把测试数据的准备做成了独立的数据工厂服务。每次任务开始前数据工厂按“设备时间戳”的维度生成全新的测试数据——不重复是个硬标准。数据生成后通过ADB导入到测试App的沙盒或者通过接口注入到后端测试环境。数据清理同样重要。执行完毕或失败后平台会自动清理产生的数据避免污染下一次测试。我们的规则是涉及写入的操作一律记录数据痕迹套件跑完统一调用清理接口。这个机制虽然初期开发成本高但长时间跑下来因为数据污染导致的随机失败几乎清零。4.3 场景回放与AI辅助生成的尝试AutoGod做了一个十分受团队欢迎的功能手工操作录制回放。测试同学先在真机上手动走一遍流程平台通过adb采集触摸事件和系统日志自动生成步骤序列。这个序列生成的是“脚本草稿”不是最终成品关键步骤需要手动修正一下选择器。它的价值不在于直接产出完美脚本而在于把人从“从零写脚本”中解放出来录制10分钟、修正5分钟一条基础场景用例就出来了。AI辅助生成我们也在探索目前的落地是把手工录制的步骤序列丢给大模型让它结合页面结构日志生成更规范的脚本模板。不过坦白说AI生成的脚本离生产可用的程度还有距离但作为初稿生成器已经很实用。我们通常的做法是AI生成初稿 → 测试同学审查修改 → 跑通入库。修改时间平均比纯手写节省了40%左右。5. 报告与可观测性失败必须能被解释测试平台的终极价值不是“跑完告诉你过了没有”而是“跑完你能快速定位问题出在哪”。AutoGod的报告系统从第一天就定位成“取证系统”任何一次失败都必须提供完整的证据链。5.1 从堆栈到视频回放的全链路留痕每次执行平台自动收集以下数据逐步骤截图执行到哪里截到哪里设备logcat日志按进程过滤保留最近2000行完整操作视频通过scrcpy服务端录制测试框架本身的错误堆栈设备状态快照包括电量、内存、CPU占用这些数据在任务结束时自动聚合到报告详情页里并且按时间轴对齐。你点进一条失败记录可以看到在失败时间点前后发生了什么。最实用的是“失败前6秒”这个功能把视频、截图、日志三样并排展示。大多数差没过的问题我看一眼就能判断是环境问题还是业务bug。这里有个取舍值得分享全量视频录制非常占存储尤其是并行跑几十台设备时。我们后来调整为“仅失败用例保留完整视频”通过的用例只保留截图和少量日志存储成本直接砍掉了六成以上。5.2 告警分类与CI/CD无缝接入报告生成之后还要能主动把问题推给对应的人。AutoGod的告警分三级级别触发条件通知方式P0阻塞主流程用例全部失败或同一功能超过10条用例失败短信工作群电话P1严重单条关键用例失败或设备大面积掉线工作群负责人P2警告偶发失败且重试成功或某设备连续执行超时邮件工作群汇总这种分级方式让告警真正变得“可信”。大家不会因为信息轰炸而麻木。CI/CD集成也比较顺利AutoGod提供了一套webhook接口GitLab CI跑完构建后通过接口自动创建测试套件并触发执行测试跑完再把结果回传给流水线。最终实现的效果是每次提交代码自动跑一小拨冒烟用例每天凌晨跑全量回归早上团队打开报告就能决定这版本能不能上楼。5.3 质量看板的几个核心指标报告不止是当次执行的结果还要能反映趋势。AutoGod内置了一个看板我们重点盯五个指标用例通过率反映整体健康度稳定性指数即不经过重试直接通过的比例平均执行时长监控效率变化失败模块Top5定位问题集中区设备空闲率反映设备池利用是否合理看板上线后有一个显著变化团队开始主动讨论“为什么这个模块连续三天失败率最高”而不是等到发布会前一天才到处救火。自动化测试平台的价值也在这里它不但替你执行测试还告诉你问题集中在哪让研发资源的分配更有依据。6. 调优实录把AutoGod跑稳的几个关键参数平台从能跑到跑稳之间还有很长的路要走。这一节写我们实际调优过程中遇到频率最高、影响最大的几个问题每个都是踩过坑之后的总结。6.1 并发数为什么不能盲目拉满一开始我图快把40台设备全部并发跑全量回归结果执行到一半好几台设备出现adb连接超时、截图延迟、甚至系统UI无响应。排查发现不是平台逻辑问题而是USB集线器的带宽和电脑端的CPU成了瓶颈。40台设备同时截图上报告电脑端IO直接被打满处理不过来。后来我们把并发数从“设备总数”改成“可配置参数”默认值跟主机核数和存储IO挂钩。我们的经验公式是并发数CPU核数×2且单台主机同时执行的任务数不超过12个。多出设备进入等待队列前一个任务结束立刻补位。调整后整体吞吐量不降反升稳定性大幅度提高。6.2 时延抖动引起的假失败真机执行自动化时经常会有“偶发失败”同一台设备同一个用例这次过了下次挂了看日志又看不出明显异常。我们用时间分布统计后发现这类失败集中在设备负载较高的时段比如并行任务多、App在后台跑着其他进程时。Adb操作响应时间从平时的100毫秒左右被拉到500甚至800毫秒而脚本里默认的操作间隔是200毫秒就会导致点击落在错误位置。解决方案是给所有操作都加上“操作间隔自适应”每次执行前记录上一个动作的耗时如果耗时超过阈值自动拉长下一次操作的等待间隔。说白了就是动态限速给设备一点喘息时间。这个改动让我们的“偶发失败率”降了60%以上。6.3 设备长时间运行后的“亚健康”处理设备连续开机几天后即使没有任务也会出现各种奇怪问题内存碎片多、后台缓存放不下、WiFi模块休眠后唤醒慢。AutoGod做了一套“设备体检”计划每台设备每12小时自动执行一次体检脚本包括内存清理、缓存清理、后台进程杀灭、屏幕亮度复位如果连续执行失败3次自动触发重启。还有一个小的经验重启设备前先把adb服务断开等设备完全启动后再通过usb重新连接。否则adb可能识别成offline还要再折腾一轮。设备重启后平台会自动等待120秒再给它分配任务避免开机自启动的App对测试造成干扰。这套“亚健康处理”机制上线后设备平均无故障运行时间从2天提升到了10天以上。把AutoGod从能跑调到稳过程中反复出现的核心教训就一句话测试平台的职责不是把所有问题都塞给脚本开发者而是把环境噪声、设备差异、基础设施这些不确定性尽量消化在平台层。这也是我觉得“新一代安卓自动化测试平台”最该做好的事。如果你也在规划自己的自动化体系我建议先把设备层和报告层做扎实这两块看起来最不起眼却决定整套自动化流程能不能真正长期跑下去。