安卓自动化测试平台AutoGod:从设备调度到稳定性调优完整实践
2026/9/9 19:15:59 网站建设 项目流程

做安卓自动化测试平台这事,我是被逼出来的。前两年团队一直用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从能跑调到稳,过程中反复出现的核心教训就一句话:测试平台的职责不是把所有问题都塞给脚本开发者,而是把环境噪声、设备差异、基础设施这些不确定性尽量消化在平台层。这也是我觉得“新一代安卓自动化测试平台”最该做好的事。如果你也在规划自己的自动化体系,我建议先把设备层和报告层做扎实,这两块看起来最不起眼,却决定整套自动化流程能不能真正长期跑下去。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询