1. 安卓自动化测试入门:为什么选择Python+Appium?
作为一名在移动测试领域摸爬滚打多年的老鸟,我见过太多团队在自动化测试工具选型上踩坑。今天要分享的Python+Appium组合,是我经过多个项目验证后最推荐的安卓自动化测试方案。相比其他方案,它有三大不可替代的优势:
首先,跨平台能力让测试代码可以无缝运行在不同操作系统上。去年我们团队接手一个需要同时支持Android和iOS的金融APP项目,使用Appium后,70%的测试用例通过简单适配就能在双平台运行,节省了近40%的测试开发时间。
其次,语言友好性是Python的杀手锏。记得我带过的一个应届生,只用两周时间就能用Python编写完整的自动化测试脚本。这要换成Java+UiAutomator的方案,至少需要一个月的学习成本。
最重要的是生态完善度。Appium的社区活跃度在开源测试工具中常年排名前三,遇到问题时Stack Overflow上基本都能找到解决方案。上周我遇到个Android 13的权限弹窗问题,社区里当天就有大神给出了完美解决方案。
2. 环境搭建:从零开始配置测试环境
2.1 基础工具安装指南
工欲善其事,必先利其器。完整的测试环境需要以下核心组件:
-
Java JDK:Appium服务端运行依赖
bash复制# Ubuntu系统安装命令 sudo apt update sudo apt install -y openjdk-11-jdk验证安装:
bash复制java -version # 应输出类似:openjdk version "11.0.22" 2024-01-16 -
Android SDK:建议通过Android Studio安装
- 下载地址:developer.android.com/studio
- 安装时勾选:
- Android SDK Platform
- Android Emulator
- Platform Tools
- 配置环境变量:
bash复制export ANDROID_HOME=$HOME/Android/Sdk export PATH=$PATH:$ANDROID_HOME/platform-tools
-
Appium Server:推荐使用Appium 2.0
bash复制
npm install -g appium@next npm install -g appium-doctor验证安装:
bash复制appium-doctor --android # 所有✓项表示环境正常
2.2 Python环境配置技巧
创建独立的虚拟环境是必须的:
bash复制python -m venv appium_env
source appium_env/bin/activate # Linux/Mac
appium_env\Scripts\activate # Windows
安装依赖库时有个小技巧:先安装依赖较小的selenium,再装appium-python-client
bash复制pip install selenium
pip install appium-python-client
注意:如果遇到权限问题,可以加上--user参数。但在CI/CD环境中建议使用全局安装。
3. 设备连接与应用信息获取
3.1 设备连接全攻略
真机连接步骤:
- 开启开发者选项(设置→关于手机→连续点击版本号7次)
- 启用USB调试和USB安装
- 连接电脑后运行:
bash复制adb devices # 应输出设备序列号
模拟器使用建议:
bash复制# 创建Pixel 5模拟器
avdmanager create avd -n Pixel5 -k "system-images;android-33;google_apis;x86_64"
emulator -avd Pixel5 -no-snapshot-load &
3.2 应用信息提取实战
获取包名和Activity的三种方法:
-
adb命令法(适合已安装应用):
bash复制adb shell dumpsys window | grep -E 'mCurrentFocus' -
APK解析法(适合未安装应用):
bash复制
aapt dump badging app.apk | grep package aapt dump badging app.apk | grep launchable-activity -
Appium Desktop法:
- 启动应用后使用Inspector查看
- 优点:可视化操作,适合新手
4. 测试脚本开发实战
4.1 基础脚本编写
完整的测试脚本应包含以下结构:
python复制from appium import webdriver
from appium.webdriver.common.mobileby import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class AndroidTest:
def setup(self):
self.desired_caps = {
"platformName": "Android",
"deviceName": "Pixel5",
"app": "/path/to/app.apk",
"automationName": "UiAutomator2",
"newCommandTimeout": 300
}
self.driver = webdriver.Remote(
"http://localhost:4723/wd/hub",
self.desired_caps
)
def test_login(self):
# 显式等待优化写法
wait = WebDriverWait(self.driver, 15, poll_frequency=0.5)
username = wait.until(
EC.presence_of_element_located(
(By.XPATH, '//*[@resource-id="username"]')
)
)
username.send_keys("testuser")
# 密码输入最佳实践
password = self.driver.find_element(
By.XPATH,
'//android.widget.EditText[contains(@text, "密码")]'
)
password.send_keys("Test@123")
# 使用相对坐标点击(应对动态元素)
login_btn = self.driver.find_element(
By.XPATH,
'//android.widget.Button[contains(@text, "登录")]'
)
self.driver.tap([(
login_btn.location['x'] + login_btn.size['width']/2,
login_btn.location['y'] + login_btn.size['height']/2
)])
def teardown(self):
self.driver.quit()
4.2 元素定位进阶技巧
-
混合定位策略:
python复制# 先找父元素,再定位子元素 parent = driver.find_element(By.ID, 'com.example:id/parent') child = parent.find_element(By.CLASS_NAME, 'android.widget.TextView') -
滚动查找元素:
python复制driver.find_element( MobileBy.ANDROID_UIAUTOMATOR, 'new UiScrollable(new UiSelector().scrollable(true))' '.scrollIntoView(new UiSelector().text("目标文本"))' ) -
图像识别定位(Appium 2.0+):
python复制driver.find_element( By.IMAGE, base64.b64encode(open('button.png', 'rb').read()).decode() )
5. 常见问题排查手册
5.1 启动类问题
问题现象:Session not created exception
排查步骤:
- 检查Appium服务日志
- 验证设备连接:
bash复制
adb devices - 确认capabilities配置:
- platformVersion必须准确
- appPackage/appActivity区分大小写
5.2 元素定位问题
典型报错:NoSuchElementException
解决方案:
- 增加等待时间(最长不超过30秒)
- 使用更宽松的定位策略:
python复制# 模糊匹配 driver.find_element( By.XPATH, '//*[contains(@text, "部分文字")]' ) - 启用UIAutomator Viewer分析布局:
bash复制
uiautomatorviewer
5.3 性能优化技巧
-
减少截图频率:
python复制driver.update_settings({"screenshotQuality": 50}) -
禁用动画:
python复制driver.execute_script('mobile: shell', { 'command': 'settings put global window_animation_scale 0' }) -
使用缓存:
python复制elements = driver.find_elements(By.CLASS_NAME, 'android.widget.Button') login_btn = [e for e in elements if e.text == '登录'][0]
6. 企业级测试框架搭建
6.1 Page Object模式实现
推荐的分层结构:
code复制tests/
├── pages/
│ ├── base_page.py
│ ├── login_page.py
│ └── home_page.py
├── testcases/
│ └── test_login.py
└── conftest.py
示例LoginPage实现:
python复制class LoginPage:
def __init__(self, driver):
self.driver = driver
self.username = (By.ID, 'com.example:id/username')
self.password = (By.XPATH, '//*[@text="密码"]')
self.login_btn = (By.ACCESSIBILITY_ID, 'login-button')
def enter_credentials(self, user, pwd):
self.driver.find_element(*self.username).send_keys(user)
self.driver.find_element(*self.password).send_keys(pwd)
def submit(self):
self.driver.find_element(*self.login_btn).click()
def get_error_message(self):
return self.driver.find_element(
By.ID, 'com.example:id/error'
).text
6.2 集成Pytest最佳实践
conftest.py配置示例:
python复制import pytest
from appium import webdriver
@pytest.fixture(scope='function')
def driver():
caps = {
'platformName': 'Android',
'deviceName': 'Pixel5',
'app': '/path/to/app.apk'
}
driver = webdriver.Remote(
'http://localhost:4723/wd/hub',
caps
)
yield driver
driver.quit()
测试用例示例:
python复制class TestLogin:
def test_success_login(self, driver):
login_page = LoginPage(driver)
login_page.enter_credentials('valid', 'password')
login_page.submit()
assert HomePage(driver).is_displayed()
@pytest.mark.parametrize('user,pwd', [
('', 'password'),
('invalid', '')
])
def test_invalid_login(self, driver, user, pwd):
login_page = LoginPage(driver)
login_page.enter_credentials(user, pwd)
login_page.submit()
assert '不能为空' in login_page.get_error_message()
7. 云测试平台集成方案
7.1 BrowserStack配置
python复制caps = {
"platformName": "Android",
"device": "Google Pixel 7",
"os_version": "13.0",
"project": "My Project",
"build": "Android Regression",
"name": "Login Test",
"browserstack.user": "YOUR_USERNAME",
"browserstack.key": "YOUR_ACCESS_KEY"
}
driver = webdriver.Remote(
"http://hub.browserstack.com/wd/hub",
caps
)
7.2 本地设备农场搭建
使用STF(Smartphone Test Farm):
bash复制docker run --name stf \
-p 7100:7100 -p 7400:7400 -p 80:80 \
-e "SECRET=YOUR_SECRET" \
openstf/stf
连接设备后,通过ADB over TCP连接:
bash复制adb connect 192.168.1.100:5555
8. 持续集成实战
8.1 GitHub Actions配置
yaml复制name: Android CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '11'
- name: Install dependencies
run: |
sudo apt-get -y install android-sdk
npm install -g appium
pip install -r requirements.txt
- name: Start Appium
run: |
appium &
sleep 10
- name: Run tests
run: |
pytest tests/ -v
8.2 测试报告生成
使用Allure框架:
python复制# 安装
pip install allure-pytest
# 运行测试
pytest --alluredir=./allure-results
# 生成报告
allure serve allure-results
在企业级实践中,我通常会结合Jenkins Pipeline实现完整的CI/CD流程:
groovy复制pipeline {
agent any
stages {
stage('Build') {
steps {
sh './gradlew assembleDebug'
}
}
stage('Test') {
steps {
sh 'appium &'
sh 'pytest tests/ --alluredir=./allure-results'
}
}
stage('Report') {
steps {
allure includeProperties: false,
jdk: '',
results: [[path: 'allure-results']]
}
}
}
}
9. 性能测试扩展
9.1 启动时间测试
python复制# 冷启动测试
start_time = time.time()
driver.launch_app()
print(f"冷启动耗时:{time.time()-start_time:.2f}s")
# 热启动测试
driver.background_app(5)
start_time = time.time()
driver.launch_app()
print(f"热启动耗时:{time.time()-start_time:.2f}s")
9.2 内存监控
python复制# 获取内存信息
memory_info = driver.execute_script(
'mobile: shell',
{'command': 'dumpsys meminfo com.example.app'}
)
print(memory_info)
10. 测试策略建议
在长期项目实践中,我总结出几点关键经验:
-
分层测试策略:
- 单元测试:覆盖核心业务逻辑
- 组件测试:验证单个功能模块
- E2E测试:关键用户旅程验证
-
稳定性提升技巧:
- 为所有操作添加合理的等待时间
- 实现自动重试机制
- 定期清理测试环境
-
团队协作规范:
- 统一元素命名规范(如login_btn)
- 建立测试数据管理方案
- 定期review测试用例
这套Python+Appium的测试方案已经在我们的电商APP项目中稳定运行3年,累计执行测试用例超过50万次,发现有效缺陷1200+。特别是在每次大版本发布前的回归测试中,能节省约75%的测试人力成本。