资讯动态

模拟器与真机协同的移动端自动化测试方案

发布时间:2026/9/9 21:02:04 来源:尧图企业网站定制
做移动端自动化测试的同学大概率都经历过这种尴尬模拟器上跑得好好的用例一上真机就翻车或者为了验证一个功能IT 那边临时借来一筐真机插上数据线手动跑半天。我这边团队之前也在这个坑里耗了很久后来才慢慢趟出一条相对靠谱的路子——不搞“模拟器替代真机”也不搞“所有用例真机全量跑”而是把真机和模拟器放在同一个自动化体系里按设备特性分配用例协同完成从冒烟到回归的整个流程。这篇文章就聊聊这套协同方案怎么落地涉及设备管理、用例打标、调度策略、CI 集成还有一堆踩过的坑。1. 为什么“协同”而不是“二选一”1.1 模拟器的优势与硬伤模拟器在自动化测试里最大的价值就一个字快。我在本地起一个雷电模拟器实例从启动到进入桌面不超过半分钟开三个多开实例并行跑用例也才占一台 16G 内存的机器。模拟器还支持直接通过adb connect 127.0.0.1:端口号连接到设备池不像真机那样要跟 USB 口和驱动纠缠远程机器也能轻松接入。对 CI 环境来说这几乎是唯一可行的方案因为机房里的机器不可能给你配一排手机当“测试外设”。但模拟器的硬伤也很明显尤其这几年做业务遇到的坑越来越多定位、陀螺仪、重力感应这类传感器 API模拟器要么不支持要么给的数据是假的。你没法在模拟器上验证“用户走到某个地理围栏附近触发弹窗”这种真实场景。微信、支付宝等超级 App 的登录态在模拟器上经常被识别为风险环境有些业务按钮压根不出现在界面上自动化脚本直接迷路。性能数据和真实设备差距很大模拟器上 1 秒渲染完的画面真机上可能发呆 3 秒导致等待超时。系统版本碎片化不够。你可以在云端租到 Android 8 到 Android 14 的真机但模拟器通常只提供最新的一两个系统镜像老版本兼容性问题根本覆盖不了。1.2 真机不可替代的场景真机的不可替代性主要体现在“模拟器给了假答案”的场景。比如常见的小程序自动化开发者工具里明明能跑通的逻辑一到真机就报net::ERR_CONNECTION_RESET再比如地图类功能开发工具直接提示“movetomap location: fail 开发者工具暂时不支持此 API 调试请使用真机”。这些场景不是脚本写得不好而是环境本身就限制了能力再怎么写也没用。我的做法是把业务用例先按“是否依赖真机能力”分个类纯 UI 操作、表单校验、列表加载这类用例模拟器完全够用但凡是涉及定位、扫码、推送、指纹、第三方支付、弱网、系统权限弹窗交互的用例一律拉到真机池跑。这么做不是因为模拟器跑不过而是模拟器跑了也没意义报“通过”的结果只会给人虚假的安全感。1.3 协同方案的核心策略方案的核心不复杂就是把设备视为“可调度的资源”而不是把用例绑定在某一台设备上。我在 pytest 框架里实现了三层机制用例打标用pytest.mark.simulator_only、pytest.mark.real_only、pytest.mark.both区分设备偏好。设备注册把所有可用设备包括模拟器实例和真机登记到一个简单的设备池脚本启动时拉取设备列表和当前状态。调度分流根据用例标签按策略把任务发到对应的设备组执行。这样组合下来日常提交代码后先在模拟器组快速跑一遍冒烟花费不到 5 分钟代码合入后再在真机池上跑完整回归。两套环境共用一套用例和维护体系按设备和用例特性分流而不是“两套环境两套脚本”互相打架。2. 整体架构与工具选型2.1 主框架选择Appium 还是 Airtest移动端自动化框架市面上数得出来的不少但真正适合搭建协同方案的我这里主要推荐两条路线Appium 和 Airtest。两者定位不同选择关键看场景。我在早期团队用 Appium 比较多因为它基于 WebDriver 协议语法对做过 Web 自动化的人很友好跨 App、跨平台能力强Android 和 iOS 都能覆盖。缺点是环境配置重依赖多而且定位元素在某些自定义控件上不太稳定。后来接手了一个游戏业务的项目Appium 就力不从心了因为游戏里大多是自绘 UI没有原生控件树可查这时候 Airtest 就派上了用场。Airtest 的强项是图像识别 少量 UI 结构识别直接截图找图逻辑简单上手快而且对模拟器和真机的适配都做得不错。对比项AppiumAirtest定位方式原生控件树为主图像识别 控件识别适用场景原生 App、跨平台 UI 自动化游戏、自绘 UI、混合应用环境依赖较重需要 Node、Java、Appium Server轻量Python 环境即可多设备支持通过 Appium Grid 支持较好通过多开脚本支持较好学习成本中高较低我在实际项目里的建议是如果你维护的是普通 App 或小程序优先 Appium如果你的业务大量依赖自定义绘制、或者 UI 频繁换皮导致控件定位不稳定Airtest 可能更省心。协同方案本身并不绑定特定框架只要框架能支持多设备并发调用即可。2.2 设备管理与调度层设备管理是协同方案的“地基”没有这一步后面的分流和并发都是空中楼阁。我的做法是维护一个轻量的设备注册中心本质上就是一个 JSON 配置文件加一个定时扫描脚本{ devices: [ { name: 雷电模拟器-1, type: emulator, address: 127.0.0.1:7555, capabilities: [basic_ui, form], status: online }, { name: 小米10-真机-01, type: real_device, address: 192.168.1.101:5555, capabilities: [basic_ui, location, push], status: online } ] }调度层不用做得太重我直接用了自研的调度脚本外加一个 Redis 队列用例跑起来后调度器根据用例标签去设备注册中心查当前可用的设备组然后把任务投递到队列每台设备空闲了就取下一条任务执行。相比直接套用现成的 Selenium Grid这套轻量方案带来的额外代码量不大但胜在可控性强后续要加“按区域分配设备”或“设备故障自动隔离”都方便。2.3 用例组织与打标用例组织是整个协同方案里最容易被忽视、但影响最大的环节。我在项目里用了 pytest 的标签机制把用例按设备能力拆成三个层级冒烟标签smoke核心的登录、首页加载、关键按钮点击这些用例必须在 5 分钟内跑完全部可以放在模拟器上。主流程标签core_flow支付、下单、消息推送等核心业务链路需要真机环境验证放在真机池跑。回归标签full_regression覆盖所有功能模块模拟器和真机各占一部分通过数据驱动参数区分设备偏好。具体到用例代码加上装饰器就完事了import pytest pytest.mark.smoke pytest.mark.simulator_only def test_login_success(): 登录流程冒烟测试 ... pytest.mark.core_flow pytest.mark.real_only def test_pay_with_fingerprint(): 指纹支付——只能在真机验证 ... pytest.mark.full_regression pytest.mark.both def test_order_list_pagination(): 订单列表分页真机模拟器都跑 ...这样打标之后CI 里执行命令就非常清晰后面细说。3. 环境准备与设备接入实操3.1 模拟器集群的配置细节模拟器接入自动化首要问题是端口和 adb 连接。以雷电模拟器为例它默认的 adb 端口是 5555但如果你开了多开实例每个新实例的端口会递增比如 5557、5559。连接命令很简单adb connect 127.0.0.1:7555 adb devices -l但有几个细节需要提前处理否则后面跑起来很头痛固定模拟器分辨率不要用默认的 1280x720 这种带虚拟按键的分辨率建议固定为 1080x1920 或 1440x2560并且关闭虚拟导航栏。UI 自动化最忌讳的就是设备分辨率不一致同一行文字在不同屏幕密度下换行位置不同元素定位直接漂移。开启 root 权限模拟器上建议开启 root这样可以配合 adb 命令去修改系统设置、清后台、模拟弱网等。真机上这些操作受限制模拟器反倒方便。关闭窗口动画通过设置里的“开发者选项”把窗口动画缩放、过渡动画缩放、动画程序时长缩放全部调成关闭状态。否则每次点击操作都带有几百毫秒的动画脚本要么等待超时要么因为动画打断了后续操作而报错。多开实例的 adb 连接雷电提供一个ldconsole命令行工具可以列出所有实例和它们的运行状态我通常在 CI 机器上通过ldconsole启动指定数量的实例再逐个adb connect保证设备池就绪。3.2 真机设备接入与清理真机接入的麻烦程度比模拟器高一个量级但只要把流程标准化也能做到“插上就能跑”。我这边真机池的设备是通过 WiFi 连接的减少布线复杂度。每台手机提前打开“开发者选项”里的“USB 调试”和“WiFi 调试”或通过 USB 先执行adb tcpip 5555再拔线通过adb connect连接。连接之后的注意事项设备命名要规范我会在设备注册中心用“品牌-型号-用途-编号”方式命名比如小米-10-真机回归-01避免用设备序列号这种无意义的 ID 去辨认。清理脚本需要沉淀真机跑完一轮用例后必须恢复初始状态。我的清理脚本做三件事卸载安装的测试包、清除应用数据、恢复系统设置比如关闭飞行模式、重置权限弹窗状态。如果不清理下一轮用例会因为上一轮留下的弹窗、登录态、通知消息而失败。测试账号隔离真机上如果复用同一个测试账号很容易出现“其中一台设备把另一台挤下线”的情况尤其是微信登录场景。我在方案里给每台设备分配独立的账号并把账号信息维护在配置中心保证用例幂等。3.3 统一驱动的封装为了让用例不关心底层跑在模拟器还是真机上我封装了一层DeviceDriver把启动、点击、输入、截图等操作统一收口class DeviceDriver: def __init__(self, device_info): self.address device_info[address] self.type device_info[type] self.appium None # 实际初始化时根据框架选择 def start_app(self, package, activity): # 启动应用 pass def click(self, locator): # 点击元素或坐标 pass def screenshot(self, save_path): # 截图用于失败归档 pass def get_device_log(self): # 抓取 logcat 日志方便排查崩溃 pass用例层的代码不需要关心self.device到底是模拟器还是真机因为 DeviceDriver 内部已经处理了差异。这个封装踩了不少坑才逐渐稳定比如有的模拟器上点击坐标需要乘一个分辨率缩放系数真机则不需要这些差异在驱动层消化掉不污染业务用例。4. 核心协同流程从开发到发布的自动化4.1 用例分层与调度规则实战调度规则定下来之后整个流程就变得机械而可靠。我在 CI 流水线里配置了三个阶段的自动化测试任务提交触发MR / PR 触发只跑smoke且simulator_only的用例。这些用例数量控制在 50 条以内全部丢给模拟器池目标是在 5 分钟内给出“核心功能是否被破坏”的初步结论。合入触发Merge 到主分支跑core_flow用例全部丢给真机池。这里的关键业务链路需要真实设备能力耗时通常 20 到 30 分钟。每日夜间回归执行full_regression全部用例按用例标签自动分配给模拟器和真机。模拟器组负责覆盖率和并发速度真机组负责关键链路和真机专项能力。调度命令大概长这样# 模拟器冒烟 pytest -m smoke and simulator_only --device-pool emulator \ --alluredir./reports/smoke # 真机核心链路 pytest -m core_flow and real_only --device-pool real_device \ --alluredir./reports/core # 夜间全量回归 pytest -m full_regression and (simulator_only or real_only or both) \ --device-pool all --alluredir./reports/full这套调度方式的收益是“弹性”白天提交多模拟器池可以轻松扩容到 10 个实例并行每次跑到峰值的消耗也不大夜间回归真机不足时还能把部分低风险用例临时降级到模拟器用“覆盖率换时间”。4.2 结果回传与报告聚合设备多了结果聚合就容易乱。我在方案里规定每台设备执行完用例后统一输出 JUnit XML 和 Allure 原始数据然后由报告服务做聚合。针对失败用例我会自动收集三件套失败瞬间的设备截图设备端 logcat 日志最近 200 行应用崩溃堆栈如果检测到 crash这三样数据会随失败记录一并上传测试人员打开报告就能直接定位不需要去翻设备日志。实践下来“失败现场信息是否完整”决定了排查一个 bug 的时长之前未做采集时排查一个偶现问题要半小时现在基本控制在 5 分钟以内。4.3 与 CI/CD 集成协同方案要真正“跑起来”而不是躺在本地脚本里必须接到 CI 流水线。我这边用的 GitLab CIpipeline 大概是这样stages: - smoke_test - real_device_test - full_regression smoke_test: stage: smoke_test script: - python run.py --mode smoke tags: - emulator-runner real_device_test: stage: real_device_test script: - python run.py --mode real tags: - real-device-runner full_regression: stage: full_regression script: - python run.py --mode full tags: - emulator-runner - real-device-runner only: - schedules有几个 CI 集成时的细节值得提醒CI 机器上要常驻一个“设备巡检服务”每 5 分钟检查模拟器进程是否存活、真机 adb 是否在线。设备掉线后要自动恢复该重启的重启该重新连接的重新连接。失败率监控很重要。我设置了阈值模拟器组失败率高于 10%真机组失败率高于 5%就自动阻塞发布流程。这个比例不是拍脑袋定的而是根据历史三个月数据算出来的 99 分位值避免偶尔的设备抖动阻塞上线。CI 流水线里的测试任务尽量做成可重跑的。偶发的网络超时、App 闪退导致的失败不要拒绝重试。我这边对“可重试类型的失败”最多重试 2 次减少人工介入。5. 高频问题与排查实录5.1 模拟器不支持特定 API怎么提前发现文章开头提到的地图类 API 报错很典型开发工具里调用.movetomap location直接提示“开发者工具暂时不支持此 API 调试请使用真机”。这类问题如果在冒烟阶段才暴露其实已经晚了。我的做法是建立“环境能力探测”预检查在每轮测试开始前脚本会先向设备发送一组环境探测用例包括是否能调用定位接口是否支持指纹/人脸识别是否存在微信登录风险弹窗探测结果写入设备的能力档案调度器分配用例时直接过滤掉“不支持的能力”。这样就不会出现用例跑了半天最后才发现设备根本不支持该 API 的尴尬。5.2 小程序真机调试连接失败常见原因net::ERR_CONNECTION_RESET这个报错做小程序自动化的同学应该都烦过。真机调试时经常是首次连接成功第二次就连不上报连接被重置。排查下来主要原因有两个开发者工具和手机端的调试基础库版本不匹配。工具升级后手机端微信小程序调试组件需要同步更新否则旧版本无法建立长连接。本地代理冲突。开发者工具默认走系统代理如果 CI 机器上代理配置不合规比如挂了公司代理请求会被重置。解决办法统一固定开发者工具版本不要随便升CI 机器上关闭系统代理或设置白名单确保servicewechat.com和相关调试域名可以直连。另外还有一个常见原因真机上“不是小程序开发者”无法开启调试权限。处理方式是在小程序后台把相关测试微信号加到开发者白名单不然代码里写再多 debug 配置也白搭。5.3 非预期弹窗导致的用例失败自动化测试最大的敌人不是功能 bug而是“非预期弹窗”。新版用户协议弹窗、积分红包弹窗、App 更新提示弹窗、系统权限弹窗……任意一个都能让精心写好的定位直接失效。我的解法分三层第一层设备层面提前处理。通过 adb 命令预设“允许所有权限”同时关闭 App 的新版本检测提示。第二层脚本里加“弹窗雷达”。在点击任何元素前先快速检测屏幕上是否存在已知弹窗特征按钮文案、控件 ID存在就优先关闭。第三层重试机制。如果弹窗雷达也没有覆盖到新弹窗用例失败后自动点击设备“返回键”或“Home 键”并重试一次。这三层叠加之后弹窗导致偶发失败的比例从 20% 降到了 3% 左右剩余的 3% 基本是新弹窗首次出现人工补录一次即可。5.4 设备掉线、端口冲突与账号互踢设备多了“掉线”是家常便饭。模拟器用久了内存暴涨直接卡死真机 WiFi 信号不好adb 连接断开。我的处理策略超时重连设备池巡检脚本发现设备失联超过 30 秒自动执行adb connect重新接入失败则重启模拟器或提示人工检查真机。端口冲突多台模拟器同时连接时分给每台实例固定的 adb 端口并在注册中心做好端口占用记录避免冲突。账号互踢微信登录类用例分开设备账号配置中心维护每台设备的账号清单一条用例用完后强制退出登录并清理 token防止设备间的状态互相污染。5.5 结果稳定性最后补充一个容易被忽略的点用例的稳定性依赖“等待策略”而不是无脑sleep。之前团队有人图省事在每个操作后都加 3 秒固定等待结果用例执行时间翻倍而且一上真机就超时。我现在强制使用显式等待元素出现等 5 秒不存在再抛超时页面跳转等待目标 Activity 出现网络请求等待接口返回。这一步看起来不起眼实际上对真机模拟器混合跑的场景至关重要因为真机和模拟器的响应速度差异很大固定等待要么模拟器上浪费时间要么真机上等待不足。6. 协同方案的长期演进方案跑通之后后面可以持续投入的方向我这里给出几个明确的建议。6.1 从“设备池”到“云真机”自建真机池的物理瓶颈迟早会遇到设备数量有限、硬件维护成本高、机型覆盖不全。不少团队会选择接入云真机服务把部分用例调度到云端设备执行。协同方案里的设备注册和调度层其实天然支持这种扩展——把云设备也当作“远程 device”注册进池子即可。我建议先统计本地设备池的利用率如果超过 80%再考虑引入云真机作为弹性资源池不要一上来就全量迁移。6.2 引入 AI 辅助定位传统控件定位在 UI 频繁改版时维护成本很高近几年 AI 辅助测试的实践也越来越多。我在方案里留了一个“第二定位通道”当控件定位失败时尝试通过 Airtest 的图像识别找元素位置两者都失败才报错。这样改版后即使原生控件属性变化只要界面元素长得以往相似用例仍然能继续跑。这个配合在模拟器和真机上都表现不错但要注意图片素材需要针对不同分辨率分别准备否则识别率会掉。6.3 数据构造与状态管理测试用例的稳定性很大程度上取决于数据是否干净。我之前踩过最大的坑就是“共用测试环境”A 用例改了一条订单状态B 用例依赖的数据就找不到了。后来我在方案里加入了“测试数据即代码”的概念每条用例开始时通过接口或数据库脚本独立构造所需数据用例结束后清理。这样即使模拟器和真机并行跑同一套用例也不会互相干扰。6.4 环境巡检与健康分给每台设备算一个“健康分”是一个值得尝试的小创新。我这边每台设备在跑完一轮用例后会根据用例通过率、执行时长、CPU 和内存占用给设备打分。低于 85 分的设备自动从调度池中下线减少带病设备对结果的影响。这个方法实施后的一个季度整体用例通过率提升了 12%本质上就是“不让一台状态异常的机器拖垮整个测试信号”。最后分享一个我在日常维护里最大的体会真机和模拟器的协同本质上不是技术选型问题而是测试资源调度问题。不要试图让某一种设备覆盖所有场景而是明确各自的适用范围然后用同一套用例基线把它们串起来。做顺了以后开发提测到冒烟反馈的时间能压到 5 分钟以内回归跑得稳稳当当测试人员也能从“手动点手机”里解放出来把时间花在真正需要人工判断的探索性测试上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价