Python安卓自动化测试从零到一:Appium+Pytest完整实战指南

发布时间:2026/10/10 10:12:22
Python安卓自动化测试从零到一:Appium+Pytest完整实战指南 如果你还在手动一条条点手机上的App测试用例大半夜盯着屏幕重复同一个登录流程点在手指发麻——这篇文章大概率是为你写的。我在HoRain云上梳理带新人做安卓自动化测试的完整过程最想说的第一句是Python这套技术栈确实把安卓自动化测试的入门门槛拉低了一大截。你不需要懂复杂的Java反射不需要啃Android原生接口只要会写Python、看得懂adb命令就能把一套完整的自动化测试框架搭起来让手机自己跑用例、自己截图、自己出报告。这篇文章是围绕“Python 安卓 自动化测试”整个链路从零到一的攻略整理适读人群主要三类刚接触移动端自动化、想入门的测试开发用Pytest写过Web端用例、想横向扩到App端的人以及准备做自动化测试面试、需要一套实操话术的人。我会把选型理由、环境搭建、核心原理、第一个用例、工程化组织再到调试稳定性整条链路按真实踩坑的顺序走一遍。1. 选型判断为什么是Python而不是Java或JavaScript1.1 Python在安卓自动化生态里的真实地位先说结论Python不是唯一能做安卓自动化的语言但它确实是综合成本最低的选择之一。早年做App自动化很多公司还在用Java JUnit Appium那套写法代码量多、学习曲线陡一个登录用例能写上几十行模板代码。Python的写法可以把同样的事压缩到十几行而且语义基本是自然语言级的click、send_keys、断言读起来跟操作步骤一样非常契合测试工程师非全栈开发的背景。再从生态角度讲移动端自动化的几个主流工具都对Python有很好的支持。Appium官方提供python-client接口与Selenium一脉相承会Web端自动化的人几乎零成本上手uiautomator2直接就是Python原生的库底层走的是基于grpc的驱动轻量高效Airtest的脚本语言同样用Python语法Poco控件树更是把Python的遍历能力利用得很彻底。可以说只要选Python你在工具链上基本不会遇到“官方不支持”的尴尬。还有一点是工程化层面的Python的Pytest框架在测试领域几乎是默认标配fixture、参数化、插件体系、Allure报告这些Web测试里成熟的能力直接平移给App测试用。Java技术栈要做到同等工程化水平需要额外学习的东西多得多。1.2 主流工具对比Appium / uiautomator2 / Airtest我整理了一张工具对比表方便你按自己的项目情况做选择。工具底层驱动上手难度跨平台典型场景AppiumUiAutomator2 / XCUITest中等iOS Android企业级测试框架、跨端团队uiautomator2自研grpc WDA低Android为主轻量脚本、数据采集Airtest图像识别 Poco最低Android / Windows游戏测试、弱控件场景做选型时我喜欢问三个问题。你的团队未来要不要测iOS如果确定要Appium是稳妥选择一套WebDriver思路两端通用。你的项目是普通业务App还是游戏游戏App大量使用自绘UI控件树拿不到Airtest的图像识别反而更适合。你只是想快速跑个POC、验证设备连通性那uiautomator2的pip install然后直接连手机操作半个小时内就能出活。这篇攻略主线以Appium展开原因是我认为它对Web测试出身的人最友好技能可迁移性也最强。但文中讲到的adb命令、Pytest工程化、稳定性处理换到uiautomator2同样适用。1.3 自动化测试的边界它到底能做什么、不适合做什么很多新人会对自动化产生两种极端误解一种觉得自动化万能能替代所有手工测试另一种跑了一两天发现处处碰壁直接放弃。真实情况在两者之间。安卓自动化测试最适合做重复度高、步骤清晰、结果可断言的场景典型的是冒烟测试和回归测试。比如每次发版前把核心路径的50个用例跑一遍这活儿让手机自己干一个晚上出结果第二天早上直接看报表。它不适合做的事情也要提前有数。纯性能测试比如CPU、内存、FPS的精确统计自动化框架虽然能拿到部分数据但专业程度远不如PerfDog这类专项工具需要操作物理传感器或硬件级手势的场景比如力度按压、多指精准角度滑动Appium的支持有限还有那种界面布局每两天变一次的开发中版本维护定位器的成本会高到让你怀疑人生。先把预期拉平后面踩坑时心态会稳很多。2. 环境搭建9 0%的新手卡在这一步不是你笨2.1 一份不踩版本坑的安装清单安卓自动化测试的环境链比Web测试长不少每一步都涉及版本匹配问题。先把清单列全再逐一解释版本选择逻辑。Python 3.8 以上推荐 3.10 或 3.11别追最新版本部分依赖库适配需要时间JDK 8 或 11Android SDK 编译工具链还依赖传统JDK太高版本反而容易出兼容告警Android SDK核心是 platform-tools也就是 adbNode.js 14 以上Appium Server 2.x 用它运行Appium Server Appium Inspector前者是服务端后者是定位元素的可视化工具安卓模拟器或真机模拟器推荐MuMu或雷电真机需要开启开发者模式这里面最容易坑人的是Python和Node.js的位数问题。老版本环境偶尔会出现32位Python调64位依赖库的鬼问题建议统一安装64位。还有JDK新手最常犯的错是装了最新的JDK 17或21结果Appium或Android工具链报各种UnsupportedClassVersionError倒不是不能用而是配置成本高出一截直接用8或11最省心。2.2 环境变量一定要手工配干净不要依赖默认值安卓自动化对系统环境变量有三个关键要求ANDROID_HOME、JAVA_HOME、PATH里要能找到adb和java。很多教程让你装完就完事结果你在终端里敲命令时一切正常但Appium启动时却报“adb not found”——原因就在于环境变量没配到系统级。可以这么做先找到SDK根目录比如Windows下是C:\Users\你的用户名\AppData\Local\Android\SdkmacOS下是~/Library/Android/sdk然后把ANDROID_HOME指到这一层。PATH里同时加上$ANDROID_HOME/platform-tools和$ANDROID_HOME/tools。JAVA_HOME则要指向JDK安装目录的根路径不是bin目录新手在这里特别容易多拼一层路径。配完之后还有个隐藏检查项随便找个终端执行java -version、adb version、echo $ANDROID_HOME三条命令确保输出都正常后再继续。这一套验证做完后续Appium起服务时的三分之二报错都不会出现。2.3 用appium-doctor做一次全身体检Appium官方提供了一个环境自检工具叫appium-doctor它的作用就是把你的环境变量、依赖库、adb连接状态一次性检查完会明确告诉你哪项缺失、哪项版本不对。安装方式是npm install -g appium-doctor装完直接跑appium-doctor它会输出一排带对勾或叉号的检查项。我遇到过最常见的warning有两类一是Android SDK内容不全比如只装了SDK Platform没装Platform-Toolsappium-doctor会提示缺少adb二是Java版本过高提示“Please set JAVA_HOME correctly”。这些提示比你自己猜要快得多。等所有项都打勾之后先别急着写脚本启动模拟器或连上真机执行adb devices确认设备在线这是我每次搭新环境雷打不动的最后一步。3. 从adb到WebDriver吃透Appium在背后到底干了什么3.1 adb是安卓自动化的地基这句话不是客套话很多教程上来就写Python脚本把adb扔到一边结果出了问题根本不知道从哪里排查。adbAndroid Debug Bridge是安卓系统提供的调试桥它的本质是一个客户端-服务端架构的命令行工具电脑通过adb向手机发送指令手机上的adbd守护进程负责执行。安装APK、启动应用、模拟点击、拉取日志、修改系统设置这些自动化脚本里的底层操作最终都是adb在干活。实际工作中我离不开的adb命令也就那么几条adb devices查看设备列表adb install -r 包名.apk覆盖安装应用adb shell am start -n 包名/Activity名启动某个页面adb logcat -s 关键词抓取应用日志定位错误adb shell input keyevent KEYCODE_BACK模拟返回键。你不需要背全命令手册但每一条都要亲手试过因为脚本一旦定位不到元素最后兜底的排查手段就是adb。3.2 WebDriver协议与三层架构Appium的逻辑框架可以理解成一个三层快递系统。第一层是你的Python脚本它扮演发件人只关心“我要点击哪个按钮、输入什么文字”第二层是Appium Server它像中转站把脚本里的操作翻译成标准的WebDriver协议指令通过HTTP转发出去第三层是手机上的UiAutomator2驱动它是执行者真正去操作界面控件。这里的关键概念是WebDriver协议。它原本是Web浏览器自动化的标准协议Appium把它迁移到了移动端所以你在脚本里看到的find_element、click这些方法底层都对应协议里的HTTP端点。a在Python脚本与Appium Server之间传递的一个核心结构是desired capabilities它本质上是一个字典配置用来告诉服务器“我要测什么平台、连哪个设备、启动哪个App”。这个概念理解透了后面看任何Appium教程都不再费劲。3.3 元素定位自动化脚本的真正难点在“找控件”不在“写代码”安卓界面上的每一个TextView、Button、ImageView在UI层次树里都是一个节点对象元素定位的本质就是在这棵树里找到目标节点。Appium常用的定位方式有ID、Accessibility ID、XPath、Class Name、Text文本。组合使用时我个人的优先级是ID Accessibility ID XPathID最稳因为包名一旦确定控件的resource-id很少变化。XPath虽然灵活但性能差、依赖页面结构android开发随便调整一下布局层级你的XPath就失效了。也是因为定位太容易失效强烈建议抛弃在线性脚本里写死坐标的做法。手机分辨率千差万别同一个按钮在1080p和2K屏上的坐标完全不同坐标点击的脚本基本没法跨机复用。老老实实定位控件最多用相对位置做辅助判断这是自动化测试能稳定跑起来的前提。4. 手把手写第一个自动化用例一个登录流程的完整落地4.1 把业务场景拆成可执行步骤先挑一个最常见的场景App登录把它拆成自动化可执行的步骤。操作路径是打开App → 点击“我的” → 点击头像/登录按钮 → 输入账号 → 输入密码 → 点击登录 → 验证首页出现用户昵称。这个用例的断言语义很清楚如果登录成功界面顶部必然冒出用户昵称这是登录成功的最直接证据。在动手写代码之前用Appium Inspector连接手机把上面涉及的元素资源ID先找出来。这个过程就像写Web自动化时按F12打开开发者工具只不过这里的工具是Appium Inspector它能实时展示当前页面的UI层级和每个控件的属性。少走弯路的方式就是先花十分钟看准元素再写代码而不是写完脚本反复跑、反复猜。4.2 desired_caps参数逐项解释下面这段代码是Appium连接安卓设备的入口配置每个参数都有明确用途from appium import webdriver desired_caps { platformName: Android, platformVersion: 13, deviceName: 127.0.0.1:7555, appPackage: com.example.demo, appActivity: .MainActivity, noReset: True, automationName: UiAutomator2, unicodeKeyboard: True, resetKeyboard: True, } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, desired_caps)逐个说参数含义platformName指定平台platformVersion是安卓系统版本deviceName指向模拟器或真机的设备标识模拟器通常是127.0.0.1:7555这类地址真机用adb devices查到的序列号appPackage是应用的包名相当于App的唯一身份证appActivity是要启动的入口Activity很多App不止一个入口写错会直接黑屏闪退。noReset: True表示不重置应用数据跑测试时保留登录态和缓存能省掉大量重复操作automationName在Android上指定UiAutomator2这是当前安卓端默认且最稳定的驱动引擎。两个Keyboard参数是为了解决中文输入问题自动化过程中调起系统输入法时不设置这两个参数很容易出现输入框收不到内容。4.3 显式等待、元素操作和断言脚本的核心三件套登录用例的完整代码可以这样写from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 打开App后先等待我的标签出现并点击 tab_mine WebDriverWait(driver, 20).until( EC.element_to_be_clickable( (AppiumBy.ID, com.example.demo:id/tab_mine) ) ) tab_mine.click() # 点击登录入口 login_entry WebDriverWait(driver, 20).until( EC.presence_of_element_located( (AppiumBy.ID, com.example.demo:id/header_login) ) ) login_entry.click() # 输入账号密码 username WebDriverWait(driver, 20).until( EC.presence_of_element_located( (AppiumBy.ID, com.example.demo:id/phone_input) ) ) username.send_keys(13800138000) password driver.find_element( AppiumBy.ID, com.example.demo:id/password_input ) password.send_keys(your_password) driver.find_element(AppiumBy.ID, com.example.demo:id/login_btn).click() # 断言首页出现用户昵称 nickname WebDriverWait(driver, 20).until( EC.presence_of_element_located( (AppiumBy.ID, com.example.demo:id/user_nickname) ) ) assert nickname.is_displayed(), 登录失败用户昵称未显示 driver.quit()这里必须专门讲一下等待策略。新手最爱写time.sleep(5)然后祈祷页面5秒内加载完。夜跑还好白天一跑加起来全崩因为测试环境的App加载速度受网络、机型、缓存影响波动极大。正确的做法是显式等待也就是WebDriverWait配合expected_conditions它会在指定时间内轮询检测元素是否出现一旦出现立即执行下一步超时才抛异常。这套机制对于处理App页面“一会儿快一会儿慢”的场景非常有效稳定性和执行速度都不错。还要注意send_keys输入密码这种事账号输入框如果之前有人登录过、App有记忆功能可能不需要重新输。所以我在输入前有时候会先调用element.clear()清空输入框避免残留值导致断言失败。5. 用pytest把脚本变成可维护的测试工程5.1 为什么选pytest而不是unittest单个脚本能跑之后第二个要解决的问题是一个App有几十条用例难道每条都复制一遍连接代码当然不是。这里就需要测试框架。Pytest对比unittest最大的优势在于fixture和参数化前者解决“每个用例的初始化和清理”问题后者解决“同一操作逻辑用多组数据跑多遍”的问题。而且Pytest断言直接用Python原生的assert失败信息直观不需要像unittest那样记一堆assertEqual的API。Pytest在测试工程师圈子里几乎是通用语言选它还有个隐性好处你写的这套用例将来做持续集成接到Jenkins或GitLab CI上时Pytest的插件生态能直接解决Allure报告、测试结果导出、失败重试等一连串需求不用自研工具。5.2 conftest.py与fixturedriver的合理创建和销毁driver对象是每条用例的公共同类资源。如果每条用例都新建连接再quit且不说慢连接本身也是Appium测试稳定性的大敌。推荐用一个conftest.py文件存放driver的fixturePytest会自动识别同目录及子目录下的这个约定文件。import pytest from appium import webdriver pytest.fixture(scopefunction) def driver(): caps { platformName: Android, platformVersion: 13, deviceName: 127.0.0.1:7555, appPackage: com.example.demo, appActivity: .MainActivity, noReset: True, automationName: UiAutomator2, unicodeKeyboard: True, resetKeyboard: True, } client webdriver.Remote(http://127.0.0.1:4723/wd/hub, caps) yield client client.quit()scopefunction意味着每个测试函数都用自己的driver实例互相隔离避免用例之间的登录态相互污染。如果suite规模上来了且用例之间不冲突可以改成scopeclass甚至scopesession提升执行速度但代价是隔离性下降需要自己评估。初期建议老老实实用function稳定优先。5.3 参数化用一组登录数据跑一条用例参数化是Pytest最惊艳的能力。同样一个登录操作需要覆盖正常密码、错误密码、空账号等多种输入场景不用写多个函数用pytest.mark.parametrize把数据传入即可。import pytest pytest.mark.parametrize(account, pwd, expect, [ (13800138000, correct_pwd, 登录成功), (13800138000, wrong_pwd, 密码错误), (, , 请输入手机号), ]) def test_login_cases(driver, account, pwd, expect): # 打开登录页 driver.find_element(id, com.example.demo:id/tab_mine).click() # 输入账号密码并登录 # 断言结果 assert expect in driver.page_source这种写法让测试数据与测试逻辑分离后面为面向观众时的数据驱动架构也这样实现。断言我用了page_source粗查快速但不精确实际项目里建议用找到具体提示控件断言文本能定位得更精准。6. 实测中跑不掉的那些坑元素定位、版本兼容、稳定性处理的完整排查链路6.1 元素定位失败的完整排查思路开始跑脚本后“找不到元素”会成为你最长见的报错。与其每个用例都对着异常猜不如把排查流程固定下来。第一次遇到这类错误先判断是“页面还没出现”还是“控件真的没有”。“页面还没出现”一般报Timeout waiting for element此时打开Appium Inspector连上看一眼当前页面确认App是不是还停在上一级或启动页。如果是加等待条件或调长超时时间即可。“控件真的没有”则要看是不是控件在WebView内。现在很多App的页面是H5混合开发的原生控件树里只能看到整个WebView容器里面的按钮要通过切换到WebView上下文才能定位处理。还有一种情况是控件资源ID是动态生成的每次打开页面都会变这种要学会找稳定父节点或改用XPath相对路径定位。这三个原因的解法差异巨大直接改等待时间反而掩盖真问题。我的建议是每次报元素找不到先像复查现场一样去Appium Inspector里做一分钟目检比对真实UI和代码里的定位表达式多数问题一眼可破。6.2 安卓版本碎片化同一个脚本在Android 9和Android 13上表现不同安卓碎片化是自动化测试的老大难。同一个desired_caps配置在Android 9上一切正常换到Android 13就可能直接报错或者行为不一致。常见差异包括权限弹窗的样式和文案变了Android不同版本对定位权限弹窗时的“允许/拒绝”按钮的资源ID不同automationName在一些大版本上有不同的稳定性表现UiAutomator2在老版本上偶发找不到控件系统设置的入口路径变化导致App启动后缺少某个权限从而直接闪退。应对办法不是追着每个版本改脚本而是用参数化驱动多设备维护一套“系统版本 设备序列号 平台版本”的矩阵配置把兼容性测试做成一个参数化用例每次发版前跑一遍几台代表机型的组合。实测中会发现Android 10和Android 11之间的差异基本可忽略大跨度版本差异才需要专门适配。6.3 真机/USB休眠导致的会话断开怎么优雅处理真机跑自动化最烦的问题之一是跑着跑着Appium连不上设备了。最常见原因是USB电源管理把手机的adb通道休眠了要么就是手机熄屏后WiFi调试断了。权限弹窗处理写一个通用关闭弹窗的小函数在关键操作前调用一次。这个函数专门负责点击界面上的“允许”或“确定”按钮不区分具体业务逻辑只要出现就点掉脚本稳定性提升立竿见影。def dismiss_system_alerts(driver): for btn_id in [com.android.permissioncontroller:id/permission_allow_always, com.android.permissioncontroller:id/permission_allow, android:id/button1]: try: btn driver.find_element(id, btn_id) if btn.is_displayed(): btn.click() except Exception: pass6.4 断点续传的坑Appium2.x与旧版语法的差异现在Appium已经升级到2.xPython客户端里的属性调用方式也变了。很多老教程里还在用driver.find_element_by_id()这类方法在Appium 2.x和Selenium 4.x里已被标记为弃用运行时会持续抛警告甚至直接报错。正确写法是用driver.find_element(AppiumBy.ID, xxx)这也是我上面代码一直采用的方式。如果你看到网上老代码报错先检查是不是这个方法签名变了。新手还有一个习惯性坑从旧项目复制一段desired_caps里面带了app字段指向本地的一个APK路径。这个字段一旦存在Appium每次连接都会尝试重新安装这个APK导致你的脚本可能一直在装包而不是跑用例。我已经不止一次帮同事排查“为什么跑得这么慢”最后发现是这个原因。用appPackage和appActivity启动已安装的应用不需要写app字段除非你的用例本来就要求先装应用。收尾让自动化测试真正跑起来的最后一个习惯写到这里技术链路上的事基本讲完了。但根据我带人和自己踩坑的经验最后一公里往往不是技术问题而是使用习惯。我自己在项目里有一个铁律每个用例第一次跑通之后立刻再连续跑三遍确认不是侥幸通过每条新加的定位表达式都要求注释里写明是哪个页面、哪个业务按钮方便三个月后的自己回来维护。这样做的回报是实实在在的稳定跑的套件会越来越壮最终在发版回归时你只需要一键触发、等报告把精力留给真正需要人判断的疑难问题。另有一个常年用的小技巧脚本里增加失败截图逻辑在except时保存当前屏幕为PNG并附上时间戳。排查问题时截图比日志快太多运维群里丢一张截图比甩一段日志有效得多。安卓自动化测试这件事说到底就是让手机替你做重复劳动把人对复杂问题的判断力留给真正需要的地方。希望这篇从选型到落地、从复现到拔坑的攻略能让你少走几段弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询