三分钟掌握UiAutomator2跨平台自动化测试实战
1. UiAutomator2是什么?能做什么?
如果你是一名移动应用开发者或者测试工程师,肯定对自动化测试不陌生。手动点击几百个按钮检查功能是否正常,这种重复劳动既枯燥又容易出错。而UiAutomator2正是为了解决这个问题而生的强大工具。
简单来说,UiAutomator2是一个基于Python的自动化测试框架,专门用于Android设备的UI自动化测试。它最大的亮点是支持WiFi连接,让你彻底摆脱USB数据线的束缚,真正实现无线自动化测试。想象一下,你可以坐在办公桌前,同时控制多台设备进行测试,而不需要插拔任何线缆,这种体验简直太棒了。
我在实际项目中使用UiAutomator2已经有一年多的时间,最大的感受就是它的稳定性和易用性。相比原生的UiAutomator框架,它不需要将测试代码打包成Jar包再上传到设备,而是直接通过Python脚本就能控制设备,大大提升了测试效率。
这个框架适合哪些人呢?首先是移动应用测试工程师,可以用它来做回归测试、兼容性测试;其次是开发人员,可以在开发过程中快速验证UI功能;还有就是需要批量操作手机的用户,比如社交媒体运营人员需要同时管理多个账号。
2. 环境准备与安装
2.1 安装必要的依赖
在开始使用UiAutomator2之前,我们需要先准备好运行环境。我推荐使用Python 3.7或更高版本,因为这个框架对Python 3的支持最好。
首先安装核心库:
pip3 install -U uiautomator2
这个命令会安装最新版本的UiAutomator2库及其依赖。我在安装过程中发现,有时候会因为网络问题导致安装失败,这时候可以尝试使用国内的镜像源:
pip3 install -U uiautomator2 -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后,我们还需要安装设备端需要的组件。传统做法是使用初始化命令:
python3 -m uiautomator2 init
但这个命令现在其实已经不需要手动执行了。从1.3.0版本开始,当你第一次连接设备时,框架会自动检测并安装所需的组件,包括uiautomator-server、atx-agent、minicap和minitouch等。
2.2 设备连接设置
接下来需要准备Android设备。无论是真机还是模拟器都可以,但需要开启开发者选项和USB调试功能。不同设备的开启方式略有差异,一般在设置->关于手机中连续点击版本号7次,就能开启开发者选项。
连接设备后,确保adb devices命令能够识别到设备:
adb devices
List of devices attached
emulator-5554 device
如果你使用模拟器,我推荐夜神模拟器或者官方模拟器。真机的话,任何Android 5.0以上的设备都可以。在实际测试中,我发现某些定制化系统的设备可能需要额外的权限设置,比如需要允许悬浮窗权限或者安装未知来源应用的权限。
3. 无线连接与实时投屏
3.1 配置WiFi连接
这是UiAutomator2最吸引人的特性之一——完全无线化的测试环境。传统的USB连接方式虽然稳定,但限制了设备的移动性,而且当需要同时测试多台设备时,线缆管理简直就是噩梦。
配置WiFi连接的第一步是确保设备和电脑在同一个局域网内。然后通过USB线执行以下命令:
adb tcpip 5555
adb connect 设备IP地址:5555
成功连接后,你就可以拔掉USB线了!以后每次测试只需要直接通过WiFi连接即可。我在实际使用中发现,保持设备和电脑在同一个5GHz WiFi网络下,连接稳定性和响应速度都非常好。
3.2 实时屏幕监控
UiAutomator2集成了openstf/minicap,提供了实时屏幕投屏功能。这个功能对于调试和监控测试过程特别有用。你可以实时看到设备屏幕上的操作,就像远程控制一样。
配合weditor工具,你可以在电脑上直接查看UI元素的各种属性。安装weditor很简单:
pip3 install --pre -U weditor
安装完成后,直接在终端输入:
weditor
它会自动打开浏览器并显示连接界面。输入设备的IP地址或者序列号,点击Connect,就能看到设备的实时屏幕,并且可以查看每个UI元素的详细属性。
4. 元素定位与操作实战
4.1 常用的元素定位方法
在实际的自动化测试中,最关键的就是准确定位UI元素。UiAutomator2提供了多种定位方式,我根据实战经验总结出了最实用的几种:
首先是文本定位,这是最直观的方式:
d(text="登录").click()
其次是ID定位,这种方式最稳定:
d(resourceId="com.example:id/login_button").click()
还有组合定位,当单一条件无法准确定位时特别有用:
d(className="android.widget.Button", text="登录").click()
我在项目中发现,使用resourceId是最可靠的方式,因为文本内容可能会随着版本更新而改变,但ID通常保持稳定。如果确实需要使用文本定位,建议配合wait方法使用,增加等待时间来提高稳定性。
4.2 常用的UI操作
掌握了元素定位后,接下来就是各种UI操作。除了最基础的click()点击操作外,还有一些很实用的方法:
长按操作:
d(text="设置").long_click()
滑动操作:
d.swipe(500, 1500, 500, 500) # 从下往上滑动
输入文本:
d(resourceId="com.example:id/username").set_text("testuser")
清除输入:
d(resourceId="com.example:id/username").clear_text()
我在实际使用中发现,滑动操作时需要特别注意坐标点的设置。不同设备的分辨率不同,最好先获取设备信息来动态计算坐标:
# 获取设备信息
device_info = d.info
screen_width = device_info['displayWidth']
screen_height = device_info['displayHeight']
5. 编写完整的测试脚本
5.1 测试用例结构设计
一个好的测试脚本应该有清晰的结构。我通常这样组织我的测试代码:
import uiautomator2 as u2
import pytest
class TestLogin:
@classmethod
def setup_class(cls):
cls.d = u2.connect('192.168.1.100:5555')
cls.d.settings['wait_timeout'] = 20.0
def test_login_success(self):
"""测试登录成功流程"""
try:
self.d.app_start("com.example.app")
self.d(text="登录").click()
self.d(resourceId="com.example:id/username").set_text("testuser")
self.d(resourceId="com.example:id/password").set_text("password123")
self.d(text="确定").click()
# 验证登录成功
assert self.d(text="欢迎回来").exists
finally:
self.d.app_stop("com.example.app")
def test_login_failed(self):
"""测试登录失败流程"""
# 类似的测试逻辑
pass
这种结构的好处是每个测试用例都是独立的,即使某个用例失败也不会影响其他用例的执行。setup_class方法用于初始化设备连接,teardown方法用于清理测试环境。
5.2 异常处理与重试机制
在实际的自动化测试中,网络波动、应用响应慢等问题经常发生,因此良好的异常处理和重试机制非常重要:
def safe_click(element, retries=3):
for attempt in range(retries):
try:
element.click()
return True
except Exception as e:
if attempt == retries - 1:
raise e
time.sleep(1)
return False
# 使用示例
safe_click(d(text="登录"))
我还建议为关键操作添加截图功能,这样当测试失败时能够快速定位问题:
def click_with_screenshot(element, name):
try:
element.click()
except Exception as e:
d.screenshot(f"error_{name}.png")
raise e
6. 高级技巧与最佳实践
6.1 并行测试执行
当需要测试多台设备时,并行执行可以大大节省时间。我通常使用多线程来实现并行测试:
from concurrent.futures import ThreadPoolExecutor
def run_test_on_device(device_ip):
d = u2.connect(device_ip)
# 执行测试逻辑
return result
devices = ['192.168.1.100:5555', '192.168.1.101:5555']
with ThreadPoolExecutor(max_workers=len(devices)) as executor:
results = list(executor.map(run_test_on_device, devices))
这种方式可以同时在所有设备上执行相同的测试用例,特别适合做兼容性测试。需要注意的是,每台设备应该使用独立的会话,避免相互干扰。
6.2 测试数据管理
良好的测试数据管理是自动化测试成功的关键。我建议将测试数据与测试逻辑分离:
import json
class TestData:
@staticmethod
def load_test_data(test_case):
with open(f'test_data/{test_case}.json') as f:
return json.load(f)
# 使用示例
test_data = TestData.load_test_data('login_test')
username = test_data['valid_username']
password = test_data['valid_password']
对于复杂的测试场景,还可以使用数据驱动的方式,使用pytest的参数化功能:
import pytest
@pytest.mark.parametrize("username,password,expected", [
("user1", "pass1", True),
("user2", "wrongpass", False),
("", "pass3", False)
])
def test_login_with_different_data(username, password, expected):
# 测试逻辑
pass
7. 常见问题排查
在使用UiAutomator2的过程中,可能会遇到各种问题。根据我的经验,最常见的问题包括连接超时、元素找不到、操作执行失败等。
对于连接问题,首先检查设备是否在线,WiFi连接是否稳定。可以尝试ping设备IP地址来检查网络连通性。如果使用模拟器,确保模拟器的网络设置正确。
元素找不到的问题通常是因为页面加载太慢,可以在操作前添加等待时间:
d(text="登录").wait(timeout=10.0) # 等待最多10秒
d(text="登录").click()
或者使用更智能的等待方式:
def wait_for_element(selector, timeout=10):
start_time = time.time()
while time.time() - start_time < timeout:
if selector.exists:
return True
time.sleep(0.5)
return False
对于偶尔出现的操作失败,建议添加重试机制:
def retry_operation(operation, max_retries=3):
for i in range(max_retries):
try:
return operation()
except Exception as e:
if i == max_retries - 1:
raise e
time.sleep(1)
在实际项目中,我建议为每个测试用例添加详细的日志记录,这样当出现问题时可以快速定位。可以使用Python标准的logging模块,记录关键操作和异常信息。
另外,定期更新UiAutomator2库也很重要,新版本通常会修复已知的问题并提升性能。但是在升级前,最好在测试环境中验证新版本的兼容性,避免影响现有的测试脚本。
更多推荐

所有评论(0)