资讯动态

移动端自动化测试架构升级:LLM角色级模型拆分实战

发布时间:2026/10/18 6:03:59 来源:尧图企业网站定制
1. 从一个模型打天下到角色级拆分移动端自动化测试的架构拐点移动端自动化测试这件事做过的人都懂——写脚本不难难的是脚本的脑子不够用。传统方案里我们用 Appium、UiAutomator、XCUITest 这些框架靠元素定位和硬编码逻辑驱动测试流程。页面一变脚本就废弹窗一冒流程就断。这两年大语言模型LLM进来之后情况开始变了我们可以让模型看懂屏幕、理解意图、动态决策。但新的问题马上来了——如果你把看屏幕做决策执行操作校验结果全塞给同一个模型它要么响应慢得让人抓狂要么在某个环节犯迷糊导致整条链路崩掉。这就是 DroidRun / Mobilerun 这类移动端自动化框架引入角色级模型拆分的根本原因。所谓角色级拆分说白了就是不让一个模型干所有活而是按职责把任务分给不同的模型实例每个实例只专注自己那一亩三分地。听起来像是分工合作的老道理但在 LLM 驱动的自动化测试场景里这个设计决策直接决定了整套系统能不能跑得稳、跑得快、跑得便宜。我最初接触这套思路的时候第一反应是这不是多此一举吗一个 GPT-4 级别的模型什么都能干何必拆但实际跑过几轮之后才发现拆分带来的收益远超预期。最直观的一点成本。一个完整的移动端测试用例从启动 App 到完成校验中间可能涉及几十次屏幕理解、十几次决策、若干次操作执行。如果每次都调用最贵的模型token 消耗量会非常惊人。而拆分成角色之后简单的屏幕元素识别可以用轻量模型只有关键决策节点才动用重量级模型整体成本能压下来一大截。另一个容易被忽略的点是延迟。移动端测试对响应速度有天然要求——你总不能让测试脚本每操作一步就等五六秒。角色拆分之后不同角色的模型可以并行工作比如屏幕解析和历史上下文整理可以同时进行主决策模型只需要拿到整理好的结构化信息就能快速判断。这种流水线式的设计比单模型串行处理要高效得多。还有一个更深层的原因可靠性。单一模型在处理复杂任务时容易出现注意力漂移——前面还在分析页面元素后面就开始胡编操作步骤。角色拆分相当于给每个环节加了约束每个模型只在自己的职责范围内输出出错概率大幅降低而且一旦出错排查起来也容易得多——你至少知道是哪个角色出了问题。所以这篇文章我想把 DroidRun / Mobilerun 里角色级模型拆分的设计思路、落地细节、踩过的坑以及实际调优经验完整地聊一遍。不管你是正在做移动端自动化测试的工程师还是对 LLM 驱动测试感兴趣的技术人相信都能从中拿到一些可以直接用的东西。2. DroidRun 与 Mobilerun 的角色划分逻辑谁该干什么活2.1 为什么不是按功能拆而是按角色拆很多人第一次听到角色级拆分会下意识理解成按功能模块拆——比如一个模型负责点击一个模型负责输入一个模型负责滑动。但 DroidRun / Mobilerun 的设计并不是这样。功能拆分的问题是同一个功能在不同场景下的决策逻辑可能完全不同。比如点击这个动作在登录页面是点登录按钮在弹窗里是点关闭按钮在列表页是点某个条目——如果你把点击单独交给一个模型它还是得理解整个页面上下文才能决定点哪里那拆分就没意义了。角色拆分的逻辑不一样。它拆的是认知层次而不是操作类型。具体来说DroidRun / Mobilerun 把整个自动化测试流程拆成了几个核心角色感知角色Perceiver负责看屏幕。输入是截图或 UI 层级树输出是结构化的页面描述——当前是什么页面、有哪些可交互元素、每个元素的位置和语义。决策角色Planner负责想下一步。输入是感知角色输出的页面描述 测试目标 历史操作记录输出是下一步该做什么操作。执行角色Executor负责做。把决策角色输出的抽象操作比如点击登录按钮翻译成具体的坐标点击、文本输入、滑动等底层指令。校验角色Validator负责判断对不对。在关键节点检查页面状态是否符合预期决定测试是通过还是失败。这四个角色各司其职每个角色可以用不同的模型、不同的提示词、不同的参数配置。感知角色需要的是视觉理解能力决策角色需要的是推理和规划能力执行角色需要的是精确的指令生成能力校验角色需要的是对比判断能力。不同能力对应不同模型这才是拆分的真正价值。2.2 每个角色的模型选型依据选型这件事没有绝对的标准答案但有一些实操中总结出来的经验规律。我整理了一个对照表方便你根据自己的场景做取舍角色核心能力要求推荐模型类型典型延迟要求成本敏感度感知视觉理解、元素识别多模态轻量模型低1s高决策推理规划、上下文理解通用大模型中1-3s中执行指令生成、格式转换小模型或规则引擎极低500ms极高校验对比判断、异常识别中等模型低1s中感知角色为什么推荐轻量多模态模型因为它的任务本质上是看图说话——把截图里的元素识别出来标注位置和语义。这个任务不需要复杂的推理但调用频率极高每一步操作前都要感知一次所以必须用轻量模型来控制成本和延迟。实际测试中一些专门优化过的视觉模型在这个任务上的表现并不比通用大模型差多少但速度快了好几倍。决策角色是整个系统的大脑值得用最好的模型。它需要理解测试目标、分析当前页面状态、参考历史操作、规划下一步动作。这个环节如果模型能力不够整个测试就会变得笨笨的——比如反复点同一个按钮、遇到弹窗不知道怎么处理、走错页面回不来。所以决策角色上不要省钱该用大模型就用大模型。执行角色其实最容易被低估。很多人觉得执行就是点一下随便什么模型都行但实际跑起来会发现执行角色需要把点击登录按钮这种抽象指令精确转换成tap(x540, y1200)这样的底层操作。如果转换出错点偏了后面全乱。这个角色可以用小模型但提示词要写得非常精确或者干脆用规则引擎兜底——对于常见的操作类型直接走规则匹配只有复杂操作才调模型。校验角色比较特殊。它的工作是在关键节点判断当前状态是否符合预期比如登录成功后应该看到首页、提交表单后应该看到成功提示。这个判断需要一定的语义理解能力但不需要太强的推理所以中等模型就够用。校验角色的提示词设计很关键后面会专门讲。2.3 角色之间的通信协议设计角色拆开之后它们之间怎么说话就成了一个大问题。如果通信协议设计得不好信息在传递过程中丢失或变形整个系统就会变得不可靠。DroidRun / Mobilerun 的做法是所有角色之间传递结构化数据而不是自然语言。感知角色输出的不是我看到一个登录页面上面有用户名输入框和密码输入框而是一个结构化的 JSON{ page_type: login, elements: [ {id: username_input, type: input, bounds: [100, 400, 900, 500], hint: 请输入用户名}, {id: password_input, type: input, bounds: [100, 550, 900, 650], hint: 请输入密码}, {id: login_button, type: button, bounds: [100, 700, 900, 800], text: 登录} ], popups: [], keyboard_visible: false }决策角色拿到这个结构之后输出也是结构化的{ action: input_text, target: username_input, value: test_user_001, reason: 需要在用户名输入框中填入测试账号 }执行角色再把这个结构化指令翻译成具体的底层操作。这种设计的好处是每个角色的输出都是可验证、可调试的。如果测试跑失败了你可以直接看每一步的结构化数据快速定位是感知错了、决策错了还是执行错了。提示通信协议里的字段命名要统一不要一会儿用 element_id 一会儿用 target否则模型在生成时容易混淆。建议在系统提示词里就把字段规范写死。3. 感知角色的工程细节让模型看准屏幕3.1 截图预处理不是所有像素都值得送给模型感知角色的输入是屏幕截图但直接把原始截图丢给模型是很浪费的。移动端屏幕分辨率动辄 1080x2400一张截图压缩后也有几百 KBtoken 消耗不小。而且模型对图片的理解能力有限太复杂的画面反而容易看漏关键元素。实际操作中我会做几层预处理第一层是缩放。把截图缩放到模型推荐的分辨率通常是 720p 左右。这个分辨率下文字和按钮还是清晰的但 token 消耗能降下来不少。缩放比例要根据设备屏幕尺寸动态计算不能写死。第二层是区域裁剪。如果当前页面有固定的顶部导航栏和底部标签栏而测试目标只在中间内容区那就可以把上下栏裁掉只送中间区域。这个优化在列表页特别有用能减少大量无关信息。第三层是元素标注。这是 DroidRun / Mobilerun 比较有特色的一个做法在截图之前先通过系统的无障碍服务Accessibility Service拿到 UI 层级树把可交互元素的边界框标注在截图上用不同颜色区分按钮、输入框、文本等类型。这样模型在看图的时候能直接看到这里有个按钮而不是从像素里猜。# 伪代码示意截图预处理流程 def preprocess_screenshot(raw_image, ui_tree, target_resolution(720, 1280)): # 1. 缩放 scaled resize_image(raw_image, target_resolution) # 2. 裁剪如果有固定导航栏 cropped crop_nav_bars(scaled, ui_tree) # 3. 标注可交互元素 annotated draw_element_boxes(cropped, ui_tree.interactive_elements) return annotated这三层预处理下来送给模型的图片信息密度会高很多感知准确率明显提升。我实测过不做标注直接送原图模型对按钮的识别准确率大概在 70% 左右做了标注之后能到 90% 以上。3.2 感知提示词的设计要点感知角色的提示词核心目标是让模型输出稳定、结构化、可解析的结果。这里有几个实操中总结的要点第一明确输出格式。不要只说描述这个页面而要给出具体的 JSON schema让模型按格式填。最好在提示词里给一个完整的示例模型照着抄格式的准确率会高很多。第二限定元素类型。移动端页面上的元素类型其实就那么几种按钮、输入框、文本、图片、列表、开关、复选框。在提示词里把这些类型列出来让模型从固定集合里选而不是自由发挥。这样后续处理起来方便很多。第三要求输出置信度。让模型对每个识别出的元素给一个 0-1 的置信度。后续决策角色可以根据置信度决定是否信任这个元素。置信度低的元素可以让感知角色重新看一次或者让决策角色换一种方式处理。第四处理弹窗和浮层。移动端测试最头疼的就是各种弹窗——权限申请、更新提示、广告、引导页。感知角色必须能识别出这些非预期的页面元素并在输出里单独标注。我通常会在提示词里加一句如果页面上存在覆盖在主要内容之上的浮层或弹窗请在 popups 字段中单独列出并标注其类型和关闭方式。3.3 感知失败的常见原因与修复感知角色出错通常不是模型能力问题而是输入信息不够或者提示词有歧义。我遇到过几种典型情况情况一元素被遮挡。比如输入框被软键盘挡住了一半模型只能看到上半部分识别出来的边界框是错的。解决办法是在截图前先判断键盘状态如果键盘弹出要么先收起键盘再截图要么在提示词里告诉模型键盘可能遮挡部分元素请根据 UI 树信息补全。情况二动态内容加载中。页面还在加载截图截到了半成品模型识别出来的元素位置和实际不符。解决办法是加一个等待页面稳定的逻辑——连续截两张图如果差异小于阈值才认为页面稳定了再送给感知模型。情况三相似元素混淆。比如列表里有多个编辑按钮模型分不清哪个是哪个。解决办法是在标注时给每个元素加上索引或位置描述让模型输出时带上索引而不是只输出元素类型。注意感知角色的输出一定要做 schema 校验。模型有时候会漏字段或者多写字段如果不校验直接传给下游决策角色可能会解析失败。建议用 Pydantic 或类似的工具做严格校验校验失败就重试。4. 决策角色的提示词工程让模型想对下一步4.1 决策角色的输入该包含什么决策角色是整个系统里最重的角色它的输入质量直接决定了输出质量。我通常会给它喂这几类信息测试目标当前测试用例要完成什么任务比如登录并进入首页。这个目标要写得具体不要写测试登录功能这种模糊描述。当前页面状态感知角色输出的结构化页面描述。这里要注意不要把原始截图直接给决策角色——决策角色需要的是语义化的信息而不是像素。截图留给感知角色看就够了。历史操作记录之前执行了哪些操作结果如何。这个记录不用太长保留最近 5-10 步就够了。太长的历史会占用大量 token而且对决策帮助有限。可用操作集合告诉决策角色它可以选择哪些操作类型比如 click、input、swipe、back、wait 等。限定操作集合能避免模型发明一些系统不支持的操作。约束条件比如不要点击广告遇到权限弹窗一律拒绝最多重试 3 次等。这些约束能显著提升测试的稳定性。4.2 决策提示词的结构化模板经过多轮迭代我总结了一个比较稳定的决策提示词模板大致结构如下你是一个移动端自动化测试的决策引擎。你的任务是根据当前页面状态和测试目标决定下一步操作。 ## 测试目标 {test_goal} ## 当前页面状态 {page_state_json} ## 最近操作历史 {action_history} ## 可用操作 - click(element_id): 点击指定元素 - input(element_id, text): 在指定元素中输入文本 - swipe(direction): 滑动屏幕 - back(): 返回上一页 - wait(seconds): 等待指定秒数 ## 约束条件 {constraints} ## 输出格式 请输出 JSON 格式的决策结果 { action: 操作类型, target: 目标元素ID如适用, value: 输入值如适用, reason: 决策理由, confidence: 0.0-1.0 }这个模板的关键在于把决策任务拆解成目标 状态 历史 约束四个维度让模型在一个明确的框架里做判断。实测下来这种结构化提示词比自由发挥式的提示词决策准确率能高出 20-30%。4.3 处理卡住和循环的决策策略决策角色最容易出的两个问题一是卡住——不知道该干什么输出一个 wait 或者干脆输出无效操作二是循环——反复执行同一个操作比如一直点同一个按钮但页面没反应。对于卡住我的处理策略是降级决策。如果决策角色连续两次输出的置信度都低于阈值比如 0.5就触发降级逻辑要么让感知角色重新感知一次可能是页面状态没看准要么切换到备用决策模型比如换一个更强的模型试一次要么直接执行一个保守操作比如 back 回上一页重新来。对于循环关键是检测重复。在决策角色的输入里我会把最近的操作历史做一个摘要特别标注以下操作已经执行过且无效## 无效操作记录 - click(login_button) 已执行 3 次页面无变化 - input(username_input, test) 已执行 2 次输入框内容未更新这样模型在决策时会主动避开这些无效操作尝试其他路径。如果所有已知路径都试过了还是不行就判定测试失败输出详细的失败原因。实操心得决策角色的温度参数temperature建议设低一点0.1-0.3 之间比较合适。温度太高模型容易脑洞大开输出一些奇怪的操作温度太低又容易陷入固定模式。这个参数需要根据具体模型和场景调。5. 执行与校验角色把决策落地把结果看准5.1 执行角色的翻译逻辑执行角色的工作是把决策角色输出的抽象操作翻译成移动端能执行的底层指令。这个环节看起来简单但细节很多。以点击为例决策角色输出的是click(login_button)执行角色需要从感知结果里找到login_button对应的边界框坐标计算边界框的中心点调用底层驱动Appium / UiAutomator执行点击等待操作生效页面可能有动画返回执行结果这里面有几个容易出问题的地方。坐标计算要考虑屏幕旋转和分辨率适配等待时间要根据操作类型动态调整点击按钮通常等 500ms 就够但如果是触发网络请求的操作可能要等 2-3 秒执行失败要有重试机制比如点击没生效可以尝试点击边界框的其他位置。对于输入操作执行角色还要处理输入法的差异。有些输入框需要先点击激活再输入文本有些输入框自带清空按钮要先清空再输入。这些细节如果处理不好测试就会莫名其妙地失败。5.2 校验角色的判断标准设计校验角色的任务是在关键节点判断当前状态是否符合预期。这个预期从哪来通常有三个来源一是测试用例里写死的断言。比如登录成功后应该看到首页那校验角色就检查当前页面类型是不是 home。二是决策角色指定的校验点。决策角色在执行某个操作后可以主动要求校验角色检查一下结果比如点击登录按钮后检查是否出现了错误提示。三是定期自动校验。每隔几步操作校验角色自动检查一下页面是否处于正常状态——没有崩溃、没有卡死、没有意外的弹窗。校验角色的输出通常是二值的通过或不通过。但实际使用中我会让它输出更细粒度的结果{ passed: false, expected: home_page, actual: login_page_with_error, error_message: 用户名或密码错误, suggestion: 检查测试账号是否有效 }这样当测试失败时排查起来会快很多。5.3 角色间的错误传播与隔离角色拆分之后一个角色出错会不会传染给其他角色这是设计时必须考虑的问题。我的做法是在每个角色之间加一层校验和兜底。感知角色的输出要过 schema 校验不合法就重试决策角色的输出要检查操作是否在可用集合内不在就拒绝执行角色的结果要检查是否真的生效了没生效就上报。这样设计的好处是错误会被隔离在单个角色内部不会一路传下去导致整个测试崩溃。比如感知角色这次看错了页面决策角色基于错误信息做了错误决策但执行角色发现要点击的元素不存在就会拒绝执行并上报异常系统可以触发重新感知而不是盲目执行一个无效操作。6. 模型拆分后的性能调优与成本控制6.1 并行化哪些角色可以同时跑角色拆分带来的一个额外好处是并行化变得容易了。在单模型方案里所有事情都是串行的——看屏幕、想决策、执行操作、校验结果一步一步来。拆分成角色之后有些环节可以并行感知角色在分析当前屏幕的同时执行角色可以准备下一步操作的底层指令模板校验角色可以在决策角色思考的同时先做一轮页面是否正常的快速检查历史记录的整理和摘要生成可以独立于主流程异步进行实际测试中合理的并行化能把整体测试速度提升 30%-40%。但要注意并行化不能破坏因果关系——决策必须基于最新的感知结果执行必须基于确定的决策这些依赖关系不能乱。6.2 缓存策略哪些结果可以复用移动端测试里有很多重复的感知和决策。比如连续多个测试用例都要先登录那登录页面的感知结果和登录操作的决策结果完全可以缓存起来复用。我的缓存策略是分层的页面级缓存如果当前页面类型和上次感知时相同且页面元素没有变化通过 UI 树的哈希值判断就直接复用上次的感知结果不用重新调模型。操作级缓存对于在登录页输入用户名这种确定性很高的操作第一次调模型得到结果后后续遇到相同场景直接复用不再调模型。决策模式缓存如果某个决策模式比如遇到权限弹窗就点拒绝反复出现可以把它固化成一个规则不再走模型决策。这套缓存策略下来整体 token 消耗能降低 40%-50%而且响应速度明显提升。6.3 成本监控与动态降级角色拆分之后每个角色的调用次数和 token 消耗都可以单独监控。我建议在系统里加一个简单的成本追踪模块记录每个角色每天/每小时的调用次数和 token 消耗。当成本超过预算时可以触发动态降级感知角色从多模态模型降级到更轻量的模型决策角色从大模型降级到中等模型非关键校验点跳过校验缓存策略变得更激进这种动态调整能力在跑大规模测试套件的时候特别有用。平时可以跑高精度配置赶时间或者预算紧张的时候切到低成本配置牺牲一点准确率换速度和成本。7. 实际落地中踩过的坑与应对经验7.1 角色边界模糊导致的踢皮球刚开始做角色拆分的时候我犯过一个典型错误角色职责定义得太模糊。比如判断页面是否加载完成这件事感知角色觉得这是决策角色的事决策角色觉得这是校验角色的事结果谁都不管测试就卡在那里。后来我的做法是每个角色都有一份明确的职责清单和不负责清单。感知角色只负责描述当前屏幕有什么不负责判断页面是否正常决策角色只负责决定下一步做什么不负责判断上一步做得对不对校验角色只负责对比预期和实际不负责决定下一步。边界清晰之后踢皮球的问题就没了。7.2 模型版本升级带来的连锁反应角色拆分之后每个角色用的模型可能来自不同的供应商版本升级的节奏也不一样。有一次感知角色用的模型升级了一个小版本输出格式微调了一下结果决策角色解析失败整个测试全挂。这件事给我的教训是角色之间的接口要加版本号并且要有兼容性测试。每次升级任何一个角色的模型都要跑一遍完整的回归测试确保上下游角色还能正常协作。另外接口解析要写得宽容一点——模型输出多一个字段、少一个字段不应该直接导致崩溃而是尽量做兼容处理。7.3 提示词漂移为什么昨天还好好的今天就崩了提示词漂移是 LLM 应用里很隐蔽的一个问题。同样的提示词同样的模型昨天跑得好好的今天突然就不行了。原因可能是模型供应商在后台做了微调也可能是输入数据的分布发生了变化。应对提示词漂移我的经验是第一建立提示词版本管理。每次修改提示词都要记录版本号和修改原因方便回滚。第二加输出监控。对每个角色的输出做统计监控比如感知角色输出的元素数量、决策角色的置信度分布。如果某个指标突然异常就说明可能出现了漂移。第三准备备用提示词。对于关键角色准备 2-3 套提示词一套出问题了可以快速切换。7.4 测试用例设计要配合角色拆分最后说一个容易被忽略的点测试用例的设计方式要配合角色拆分的架构。传统的测试用例是步骤式的——第一步点什么第二步输入什么第三步校验什么。但在角色拆分架构下测试用例更适合写成目标式的——告诉系统完成登录并进入首页具体怎么走让决策角色自己规划。这两种写法各有优劣。步骤式可控性强但灵活性差目标式灵活但对决策角色的要求高。我的做法是混合使用关键路径用步骤式确保稳定探索性测试用目标式发挥模型的规划能力。8. 从单机到分布式角色拆分的扩展想象角色拆分这套架构还有一个很大的扩展空间把不同角色部署到不同的机器上。感知角色需要处理图片可以部署在有 GPU 的机器上决策角色需要大模型推理可以部署在内存充足的机器上执行角色需要连接真实设备可以部署在设备农场附近。这种分布式部署的好处是资源利用率高而且可以独立扩展每个角色。如果感知是瓶颈就多加几台感知节点如果决策是瓶颈就升级决策节点的模型。整个系统的弹性会好很多。当然分布式也带来了新的挑战——网络延迟、数据一致性、故障恢复。这些就需要更复杂的工程手段来解决了。但至少从架构层面角色拆分为分布式部署打下了很好的基础。我个人在实际项目里的体会是角色拆分不是银弹它解决的是单模型扛不住复杂任务的问题但同时也引入了多角色协作的新复杂度。如果你的测试场景比较简单单模型方案可能更省事但如果你的测试场景复杂、对稳定性和成本有要求那角色拆分带来的收益是值得投入的。关键是要把每个角色的职责定义清楚把角色之间的接口设计好剩下的就是不断调优和迭代了。

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

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

免费获取报价 →
↑