黑马头条App自动化测试实战:从环境搭建到用例编写完整流程
黑马头条App自动化测试实战:从零到一的完整落地指南
如果你已经熟悉了Web自动化测试,正准备将技能扩展到移动端,那么这篇文章就是为你准备的。移动端自动化测试,尤其是针对像“黑马头条”这样的资讯类App,其挑战和乐趣与Web端截然不同。它不仅仅是换了个测试框架,而是涉及全新的环境、交互逻辑和定位策略。我花了近两个月时间,从零开始为“黑马头条”App搭建了一套完整的自动化测试体系,期间踩了不少坑,也总结了许多能直接复用的经验。今天,我就把这些实战心得,从环境搭建的细枝末节,到用例编写的核心技巧,毫无保留地分享给你。无论你是想为自己的项目引入自动化,还是希望深化移动端测试技能,这篇文章都将提供一个清晰、可操作的路径。
1. 移动端自动化测试环境搭建:避开那些“坑”
与Web自动化依赖浏览器驱动不同,App自动化测试的环境搭建更像是在搭建一座桥梁,连接你的测试脚本、测试服务器(Appium)、移动设备(或模拟器)以及待测应用本身。任何一个环节配置不当,都会导致“桥”无法通行。我的建议是,严格按照以下顺序进行,并特别注意我标注的常见陷阱。
1.1 核心组件安装与配置
首先,你需要准备好以下四个核心组件,它们构成了App自动化测试的基石:
- Java开发环境(JDK):Appium Server是基于Node.js的,但其底层依赖Android SDK,而Android SDK需要Java环境。安装JDK 8或11(LTS版本),并正确配置
JAVA_HOME和PATH环境变量。验证命令:java -version。 - Node.js与npm:作为Appium的运行时环境。从官网下载安装即可。安装后,通过
node -v和npm -v验证。 - Appium Server:这是核心的自动化“服务器”。推荐通过npm进行全局安装,以获得最新版本和便捷的升级方式。
安装完成后,你可以通过命令行npm install -g appiumappium启动服务,默认监听4723端口。对于日常开发,我更推荐使用 Appium Desktop 这款图形化工具。它不仅包含了Server,还内置了一个用于元素定位和录制脚本的Inspector工具,对初学者极其友好。 - Android开发环境(SDK):这是配置中最容易出错的一环。你不需要安装完整的Android Studio,但必须获取Android SDK Command-line Tools。
- 关键步骤:下载Android Studio后,在安装向导中,选择“Custom”安装,确保勾选了“Android SDK”和“Android SDK Command-line Tools”。安装后,找到SDK的安装路径(如
C:\Users\YourName\AppData\Local\Android\Sdk)。 - 环境变量配置:
ANDROID_HOME:设置为你的SDK根目录路径。- 在
PATH中添加:%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\tools。
- 安装必要平台工具:通过命令行工具
sdkmanager安装必要的平台和构建工具,例如:sdkmanager "platform-tools" "platforms;android-30" "build-tools;30.0.3" - 验证:运行
adb version,能显示版本号即表示成功。
- 关键步骤:下载Android Studio后,在安装向导中,选择“Custom”安装,确保勾选了“Android SDK”和“Android SDK Command-line Tools”。安装后,找到SDK的安装路径(如
1.2 模拟器与真机准备
对于“黑马头条”这类应用,测试需要覆盖不同Android版本。使用模拟器进行日常脚本开发和调试效率更高。
- 模拟器配置:可以通过Android Studio的AVD Manager创建。为“黑马头条”测试,我建议创建一个Android 9.0 (API 28)或11.0 (API 30)的模拟器,设备型号选择Pixel 3或4即可。创建时,注意在“Advanced Settings”中开启
Use Host GPU选项,这能显著提升模拟器渲染性能,使自动化操作更流畅。 - 真机连接:对于最终的性能和兼容性测试,真机必不可少。用USB连接手机后,需开启“开发者选项”和“USB调试”模式。通过
adb devices命令查看设备是否被识别。如果出现未授权,在手机上弹出的授权对话框中点击确认。
注意:无论是模拟器还是真机,确保设备在测试期间屏幕常亮(设置->显示->休眠,调整为“永不”),并且锁屏密码已关闭,否则自动化脚本可能在操作过程中被中断。
1.3 Python端测试框架搭建
服务端和移动端就绪后,我们回到“指挥官”——Python测试脚本的环境。
# 创建一个干净的虚拟环境是个好习惯
python -m venv toutiao_auto_env
source toutiao_auto_env/bin/activate # Linux/Mac
toutiao_auto_env\Scripts\activate # Windows
# 安装核心库
pip install Appium-Python-Client # Appium的Python客户端库,核心中的核心
pip install pytest # 测试框架,用于组织用例和断言
pip install pytest-html # 生成HTML测试报告
pip install allure-pytest # 生成更美观的Allure报告(可选但推荐)
pip install selenium # 虽然主要测App,但部分混合内容或后续Web测试可能用到
至此,你的自动化测试“战场”已经布置完毕。接下来,让我们进入真正的战斗——编写第一个能与“黑马头条”App对话的测试脚本。
2. 编写你的第一个Appium测试脚本:理解Desired Capabilities
一切测试脚本的起点,是与Appium Server建立会话(Session)。而建立会话的“钥匙”,就是Desired Capabilities。这是一组告诉Appium“你要测试什么、怎么测试”的键值对。对于“黑马头条”App,一个基础的启动配置如下:
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy # 推荐使用新的定位策略
def setup_driver():
desired_caps = {
# 1. 平台信息(必须)
'platformName': 'Android', # 平台,iOS或Android
'platformVersion': '11', # 设备系统版本,需与模拟器/真机一致
'deviceName': 'Pixel_4_API_30', # 设备名称,在adb devices中可查看,模拟器名或真机序列号
# 2. 应用信息(必须)
'appPackage': 'com.itcast.toutiaoApp', # 待测App的包名
'appActivity': '.MainActivity', # 待测App的启动Activity名
# 3. 会话与设备行为控制(重要)
'noReset': True, # True: 不清除App数据,延续上次会话。False: 每次启动都重置App。
'fullReset': False, # True: 安装并清除App。通常测试中与noReset配合使用。
'unicodeKeyboard': True, # 使用Unicode输入法,解决中文输入问题
'resetKeyboard': True, # 测试结束后重置回系统默认输入法
'automationName': 'UiAutomator2', # Android默认的自动化引擎,必填
# 4. 超时与性能相关(优化项)
'newCommandTimeout': 300, # 客户端命令超时时间(秒),网络慢时可调大
'autoGrantPermissions': True, # 自动授予App所需权限,避免弹窗干扰
}
# 初始化驱动,连接本机Appium Server
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
return driver
关键参数解读与避坑指南:
appPackage&appActivity:如何获取?有两个简单方法:- 安装
adb后,在设备上打开“黑马头条”App,然后执行命令:adb shell dumpsys window | findstr mCurrentFocus(Windows) 或adb shell dumpsys window | grep mCurrentFocus(Mac/Linux)。输出结果中com.itcast.toutiaoApp/.MainActivity斜杠前后即为包名和Activity名。 - 使用APK分析工具,如
aapt(在SDK的build-tools目录下)。
- 安装
noReset:这是调试脚本时的“神器”。设为True,App不会在每次脚本启动时被重置登录状态,省去了反复登录的麻烦。但在执行完整的回归测试套件时,可能需要设为False以确保测试环境纯净。unicodeKeyboard:在测试涉及搜索、评论等需要输入中文的场景时,必须设置为True,否则脚本可能无法成功输入中文字符。
现在,驱动已经初始化,App已经启动。下一步,就是教会脚本如何找到屏幕上的按钮、输入框,并与之交互。
3. 移动端元素定位的独门技巧:超越Web的思维定式
如果你从Web自动化转来,可能会习惯性地依赖id、name或css selector。在移动端,虽然部分概念相通,但定位策略和工具却有显著不同。Appium提供了多种定位方式,下表对比了它们的适用场景和优缺点:
| 定位方式 | 示例 (AppiumBy) | 优点 | 缺点与注意事项 |
|---|---|---|---|
| ID (resource-id) | AppiumBy.ID(“com.itcast.toutiaoApp:id/tv_search”) | 定位精准,速度快,首选方式。 | 并非所有元素都有唯一id,尤其是一些原生控件或自定义视图。 |
| Accessibility ID (content-desc) | AppiumBy.ACCESSIBILITY_ID(“搜索”) | 对无障碍支持友好,语义化强。 | 需要开发在代码中设置contentDescription属性,不一定所有元素都有。 |
| XPath | AppiumBy.XPATH(‘//android.widget.TextView[@text=”热门”]’) | 功能最强大,几乎可以定位任何元素。 | 性能最差,解析整个DOM树,在复杂列表中可能导致脚本执行缓慢。慎用! |
| Class Name | AppiumBy.CLASS_NAME(“android.widget.Button”) | 定位某一类控件。 | 通常不唯一,需要结合其他条件或使用find_elements取列表。 |
| Android UiAutomator | AppiumBy.ANDROID_UIAUTOMATOR(‘new UiSelector().text(“登录”)’) | Android专属,功能强大,支持滚动查找、兄弟节点定位等复杂场景。 | 语法稍复杂,但解决疑难杂症非常有效。 |
实战技巧:如何高效获取元素属性?
不要靠猜!一定要使用Appium Inspector(包含在Appium Desktop中)。启动Inspector,填入上述的Desired Capabilities,点击“Start Session”,即可实时看到设备屏幕和完整的UI元素树。点击屏幕上的元素,右侧会显示其所有可用的定位属性。
一个“黑马头条”的典型定位案例:点击底部导航栏的“我的”选项卡。
假设通过Inspector查看,发现这个选项卡没有resource-id,但有一个唯一的text属性为“我的”。此时,有几种定位策略:
from appium.webdriver.common.appiumby import AppiumBy
# 方法1:使用ACCESSIBILITY_ID (如果content-desc为"我的")
my_tab = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "我的")
# 方法2:使用ANDROID_UIAUTOMATOR (推荐,功能强大且稳定)
my_tab = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("我的")')
# 方法3:使用XPATH (作为备选,但需注意性能)
my_tab = driver.find_element(AppiumBy.XPATH, '//android.widget.TextView[@text="我的"]')
my_tab.click()
处理动态元素与等待策略
移动端网络请求和渲染可能导致元素加载延迟。必须使用显式等待来替代硬性sleep,这是编写健壮脚本的关键。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待“首页”的某个文章标题元素出现,最多等10秒,每0.5秒检查一次
wait = WebDriverWait(driver, 10, 0.5)
article_title_locator = (AppiumBy.ID, "com.itcast.toutiaoApp:id/tv_title")
article_element = wait.until(EC.presence_of_element_located(article_title_locator))
article_element.click()
# 更复杂的等待条件:等待元素可点击
login_btn_locator = (AppiumBy.ID, "com.itcast.toutiaoApp:id/btn_login")
login_btn = wait.until(EC.element_to_be_clickable(login_btn_locator))
login_btn.click()
掌握了定位和等待,你已经可以操作App中的大部分静态元素。但对于资讯类App,滑动浏览是最高频的操作,这需要特殊的API。
4. 高级交互与页面对象模式实战
移动端测试离不开滑动、长按、多点触控等手势操作。Appium将这些封装成了TouchAction和W3C Actions API。对于“黑马头条”这样的信息流App,滚动查找内容是核心测试点。
4.1 实现滚动查找
单纯靠坐标滑动不稳定,最佳实践是结合UiAutomator的滚动查找能力。
def scroll_to_find_element(driver, text):
"""
滚动屏幕直到找到包含指定文本的元素。
这是一个通用函数,可用于查找不在当前视图内的新闻条目、设置项等。
"""
# 使用UiAutomator的滚动查找语法
scroll_script = f"""
new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(
new UiSelector().textContains("{text}")
);
"""
try:
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, scroll_script)
return True
except Exception as e:
print(f"未找到包含文本 '{text}' 的元素")
return False
# 在测试用例中使用:滚动查找“科技”频道
scroll_to_find_element(driver, "科技")
# 找到后,可以继续操作该元素
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().textContains("科技")').click()
4.2 构建可维护的页面对象模型
随着测试用例增多,直接将定位器和操作写在用例里会变得难以维护。页面对象模型(Page Object Model, POM) 是解决这一问题的标准模式。其核心思想是将每个页面封装成一个类,页面的元素定位和基本操作作为类的方法,测试用例只关心业务逻辑。
以“黑马头条”的登录页面为例,我们构建一个清晰的POM结构:
toutiao_auto_project/
├── pages/ # 页面对象层
│ ├── base_page.py # 基础页面类,封装公共方法
│ ├── login_page.py # 登录页面
│ ├── home_page.py # 首页
│ └── article_page.py # 文章详情页
├── tests/ # 测试用例层
│ └── test_login.py
├── utils/ # 工具类
│ ├── driver_factory.py # 驱动管理(单例模式)
│ └── common_actions.py # 通用操作,如滚动、截图
└── conftest.py # pytest共享fixture
base_page.py (封装公共操作):
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(self.driver, 10)
def find_element(self, locator):
"""查找单个元素,并等待其出现"""
return self.wait.until(EC.presence_of_element_located(locator))
def find_elements(self, locator):
"""查找多个元素"""
return self.wait.until(EC.presence_of_all_elements_located(locator))
def click(self, locator):
"""点击元素,等待其可点击"""
element = self.wait.until(EC.element_to_be_clickable(locator))
element.click()
def input_text(self, locator, text):
"""向输入框输入文本"""
element = self.find_element(locator)
element.clear()
element.send_keys(text)
login_page.py (定义登录页面的元素和操作):
from appium.webdriver.common.appiumby import AppiumBy
from .base_page import BasePage
class LoginPage(BasePage):
# 定位器 (Locators)
PHONE_INPUT = (AppiumBy.ID, "com.itcast.toutiaoApp:id/et_phone")
PASSWORD_INPUT = (AppiumBy.ID, "com.itcast.toutiaoApp:id/et_password")
LOGIN_BUTTON = (AppiumBy.ID, "com.itcast.toutiaoApp:id/btn_login")
ERROR_TOAST = (AppiumBy.XPATH, "//android.widget.Toast")
def __init__(self, driver):
super().__init__(driver)
def enter_phone_number(self, phone):
self.input_text(self.PHONE_INPUT, phone)
def enter_password(self, password):
self.input_text(self.PASSWORD_INPUT, password)
def click_login(self):
self.click(self.LOGIN_BUTTON)
def get_toast_message(self):
"""获取Toast提示信息,常用于验证登录失败等场景"""
try:
toast_element = self.find_element(self.ERROR_TOAST)
return toast_element.text
except:
return None
test_login.py (清晰简洁的测试用例):
import pytest
from utils.driver_factory import get_driver
from pages.login_page import LoginPage
from pages.home_page import HomePage
class TestLogin:
@pytest.fixture(scope="class")
def driver(self):
"""获取驱动,整个测试类只启动一次App"""
driver = get_driver()
yield driver
driver.quit()
def test_successful_login(self, driver):
"""测试正常登录流程"""
login_page = LoginPage(driver)
home_page = HomePage(driver)
login_page.enter_phone_number("13800138000")
login_page.enter_password("your_password")
login_page.click_login()
# 验证登录成功:检查首页是否显示用户名
username_displayed = home_page.is_username_displayed()
assert username_displayed, "登录后未正确显示用户名"
def test_login_with_wrong_password(self, driver):
"""测试密码错误场景"""
login_page = LoginPage(driver)
login_page.enter_phone_number("13800138000")
login_page.enter_password("wrong")
login_page.click_login()
# 验证出现了正确的错误提示Toast
toast_msg = login_page.get_toast_message()
assert "密码错误" in toast_msg, f"期望提示‘密码错误’,实际收到: {toast_msg}"
通过POM模式,测试用例变得极其易读和维护。当登录页面的输入框ID发生变化时,你只需要修改login_page.py中的一个常量,所有相关的测试用例都会自动生效,这大大提升了测试套件的健壮性。
5. 测试报告、持续集成与最佳实践
脚本能稳定运行后,我们需要让它产生价值——清晰的结果报告,并能集成到开发流程中。
5.1 生成专业测试报告
使用pytest-html可以快速生成一份HTML报告,而allure-pytest则能生成非常美观且信息丰富的交互式报告。
# 运行测试并生成HTML报告
pytest tests/ --html=report.html --self-contained-html
# 运行测试并生成Allure结果数据
pytest tests/ --alluredir=./allure-results
# 生成并打开Allure报告 (需要先安装Allure命令行工具)
allure serve ./allure-results
Allure报告会展示用例执行趋势、分类统计、丰富的图表,甚至能附加上测试步骤中的截图,对于分析测试结果和定位问题帮助巨大。
5.2 融入持续集成流程
将自动化测试接入CI/CD(如Jenkins, GitLab CI),可以实现每次代码提交后自动执行回归测试。核心是在CI服务器上配置好Android SDK、模拟器和Appium环境,然后通过命令行触发测试脚本。一个简单的GitLab CI配置示例:
# .gitlab-ci.yml
stages:
- test
app-automated-test:
stage: test
image: python:3.9 # 使用包含Python的Docker镜像
before_script:
- apt-get update && apt-get install -y wget unzip openjdk-11-jdk
# 安装Android SDK, Node.js, Appium...
- pip install -r requirements.txt
script:
- appium & # 后台启动Appium Server
- sleep 10 # 等待Server启动
- pytest tests/ --alluredir=allure-results
artifacts:
when: always
paths:
- allure-results/
expire_in: 1 week
after_script:
- pkill -f appium # 清理进程
5.3 项目实战中的经验与避坑总结
在完成“黑马头条”项目的自动化覆盖后,我总结了以下几点关键经验,这些是教程里不常提到,却能决定项目成败的细节:
- 设备农场与并行测试:当用例数量增多时,串行执行会非常耗时。研究使用
pytest-xdist进行并行测试,并考虑使用云测平台或自建Selenium Grid/Appium Grid设备农场,在多台设备上同时运行测试。 - 测试数据管理:不要将测试账号密码硬编码在脚本里。使用配置文件(如
config.ini、config.yaml)或环境变量来管理。对于需要大量测试数据的场景,可以连接测试数据库或通过API接口预先准备数据。 - 失败分析与截图:在
BasePage的click、input_text等关键操作方法中,加入异常处理和截图功能。当操作失败时,自动截取当前屏幕保存到指定目录,并以时间戳和用例名命名,这能极大缩短问题排查时间。 - 关注非功能测试:自动化不仅能做功能验证。尝试结合
adb命令获取CPU、内存占用,或者使用Appium的get_performance_data方法,在关键业务流程中监控App的性能表现,提前发现潜在的性能瓶颈。 - 保持代码简洁:遵循“Don‘t Repeat Yourself”原则。将常用的操作(如上面提到的滚动查找)抽象成工具函数。保持页面对象只做最简单的元素定位和操作,复杂的业务组合逻辑放在单独的
Page Action类或测试用例本身。
移动端自动化测试,尤其是像“黑马头条”这样交互复杂的真实项目,是一个系统工程。从环境搭建的第一步开始,到编写出稳定、可维护的测试用例,每一步都需要耐心和对细节的关注。我最开始的时候,光是一个环境变量配置错误就折腾了半天。但当你看到一套完整的测试用例在无人值守的情况下流畅运行,自动验证核心功能,并生成一份漂亮的报告时,那种成就感和它带来的效率提升,会让所有前期的投入都变得值得。记住,最好的学习方式就是动手,从克隆一个“黑马头条”的测试Demo开始,修改它,扩展它,让它去解决你实际项目中遇到的问题。
更多推荐


所有评论(0)