使用 AirTest(UI操作)+ Pytest(测试框架)+ ddt(数据驱动)实现UI自动化测试,核心是通过 数据驱动 让同一测试逻辑适配多组输入数据,同时利用 Pytest 管理用例生命周期和报告,用 AirTest 完成设备交互和UI操作。以下是可落地的完整步骤(以Android端APP为例):

一、核心工具分工

  • AirTest:负责设备连接、APP启动/关闭、UI控件定位(点击、输入等操作)、截图日志记录。
  • Pytest:提供用例组织(类/函数)、前置/后置操作(setup/teardown)、用例执行控制、测试报告生成。
  • ddt:实现数据驱动,将多组测试数据注入同一测试方法,自动生成多条用例。

二、环境准备

1. 安装依赖库
# 核心库
pip install airtest  # AirTest基础库(含设备连接、图像识别)
pip install pocoui   # 控件识别库(poco,比图像识别更稳定)
pip install pytest   # 测试框架
pip install ddt      # 数据驱动库

# 辅助库(可选)
pip install pytest-html  # 生成HTML测试报告
pip install pandas       # 读取Excel/CSV数据(数据量大时用)
2. 设备连接(Android为例)
  • 确保电脑安装 adb 工具(配置环境变量),手机开启“开发者模式”和“USB调试”,连接电脑后执行:
    adb devices  # 查看设备列表,确保设备状态为"device"(如:127.0.0.1:62001 device)
    

三、项目结构设计(清晰可维护)

ui_auto_test/
├── config/               # 配置文件(设备、APP信息)
│   └── settings.py       # 设备连接串、APP包名等
├── data/                 # 测试数据(多组输入)
│   └── test_data.py      # 登录/注册等场景的数据
├── page/                 # 页面对象(PO模式,分离控件和操作)
│   └── login_page.py     # 登录页面的控件定位和操作方法
├── test_cases/           # 测试用例
│   └── test_login.py     # 登录功能的测试逻辑
├── reports/              # 自动生成的测试报告
└── run.py                # 用例执行入口

四、分步实现核心代码

1. 配置文件(config/settings.py)

定义设备连接信息和APP基础信息(避免硬编码):

# 设备连接字符串(格式:平台://adb服务器地址/设备ID)
# 从adb devices获取设备ID,如"127.0.0.1:62001"(夜神模拟器)
DEVICE_URL = "Android://127.0.0.1:5037/127.0.0.1:62001"

# 被测APP包名(通过adb shell dumpsys window | grep mCurrentFocus获取)
APP_PACKAGE = "com.example.myapp"  # 替换为实际APP包名
2. 页面对象(page/login_page.py,PO模式)

将登录页面的控件定位和操作封装成方法(降低用例与控件的耦合):

from poco.drivers.android.uiautomation import AndroidUiautomationPoco

class LoginPage:
    def __init__(self, poco: AndroidUiautomationPoco):
        self.poco = poco  # 传入poco控件驱动实例

    # 定位控件(通过UI Inspector获取属性,优先用id/text)
    @property
    def username_input(self):
        # 用户名输入框(替换为实际控件属性,如id="com.example.myapp:id/et_username")
        return self.poco("com.example.myapp:id/et_username")

    @property
    def password_input(self):
        # 密码输入框
        return self.poco("com.example.myapp:id/et_password")

    @property
    def login_btn(self):
        # 登录按钮
        return self.poco("com.example.myapp:id/btn_login")

    @property
    def toast_msg(self):
        # 提示信息(如"登录成功"、"密码错误")
        return self.poco("com.example.myapp:id/tv_toast")

    # 操作方法
    def input_username(self, username: str):
        """输入用户名"""
        self.username_input.wait_for_appearance(timeout=10)  # 等待控件出现(超时10s)
        self.username_input.clear_text()  # 清空输入框
        self.username_input.set_text(username)

    def input_password(self, password: str):
        """输入密码"""
        self.password_input.wait_for_appearance(timeout=10)
        self.password_input.clear_text()
        self.password_input.set_text(password)

    def click_login(self):
        """点击登录按钮"""
        self.login_btn.wait_for_appearance(timeout=10)
        self.login_btn.click()

    def get_toast(self) -> str:
        """获取提示信息文本"""
        self.toast_msg.wait_for_appearance(timeout=10)
        return self.toast_msg.get_text()

如何获取控件属性?
AirtestIDE 自带的 Poco辅助窗:打开IDE→连接设备→点击“Poco”按钮→选择对应APP→刷新控件树,即可查看控件的 id/text 等属性。

3. 测试数据(data/test_data.py)

ddt 支持的格式(列表/元组/字典)定义多组测试数据:

# 登录测试数据:(用户名, 密码, 预期提示信息)
login_data = [
    ("valid_user", "valid_pwd123", "登录成功"),  # 正常登录
    ("valid_user", "wrong_pwd", "密码错误,请重新输入"),  # 密码错误
    ("", "valid_pwd123", "用户名不能为空"),  # 用户名空
    ("invalid_user", "any_pwd", "用户不存在")  # 用户不存在
]

数据量大时? 可从Excel读取(需安装pandas):

import pandas as pd
def get_login_data_from_excel():
    df = pd.read_excel("data/login_data.xlsx")  # Excel列:username, password, expected
    return df.values.tolist()  # 转为[(u1,p1,e1), (u2,p2,e2)...]
4. 测试用例(test_cases/test_login.py)

结合 ddt 注入数据,用 Pytest 管理用例生命周期,调用页面对象完成测试:

import pytest
from ddt import ddt, data, unpack
from airtest.core.api import connect_device, start_app, stop_app, sleep
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
from config.settings import DEVICE_URL, APP_PACKAGE
from data.test_data import login_data  # 导入测试数据
from page.login_page import LoginPage  # 导入登录页面对象

@ddt  # 标记为数据驱动测试类
class TestLogin:
    poco = None  # 控件驱动实例
    login_page = None  # 登录页面对象

    @classmethod
    def setup_class(cls):
        """类级前置操作:连接设备、启动APP、初始化页面对象"""
        # 1. 连接设备
        connect_device(DEVICE_URL)
        # 2. 初始化poco控件驱动
        cls.poco = AndroidUiautomationPoco(use_airtest_input=True)  # use_airtest_input:支持输入法输入
        # 3. 启动APP
        start_app(APP_PACKAGE)
        sleep(5)  # 等待APP启动完成
        # 4. 初始化登录页面对象
        cls.login_page = LoginPage(cls.poco)

    @classmethod
    def teardown_class(cls):
        """类级后置操作:关闭APP"""
        stop_app(APP_PACKAGE)
        sleep(2)

    def setup_method(self):
        """方法级前置:进入登录页面(每个用例前执行)"""
        # 假设首页有"我的"按钮,点击后进入登录页(根据实际APP流程修改)
        self.poco(text="我的").click()
        sleep(2)

    def teardown_method(self):
        """方法级后置:返回首页(避免用例间污染)"""
        self.poco("com.example.myapp:id/iv_back").click()  # 返回按钮
        sleep(2)

    @data(*login_data)  # 注入测试数据(*解包列表)
    @unpack  # 解包元组为单个参数(username, password, expected)
    def test_login(self, username, password, expected):
        """登录测试用例:输入→点击→断言结果"""
        # 1. 输入用户名和密码
        self.login_page.input_username(username)
        self.login_page.input_password(password)
        # 2. 点击登录
        self.login_page.click_login()
        sleep(3)  # 等待结果
        # 3. 断言实际提示是否符合预期
        actual = self.login_page.get_toast()
        assert actual == expected, f"用例失败:输入[{username},{password}],预期[{expected}],实际[{actual}]"
5. 执行入口(run.py)

Pytest 运行用例并生成报告:

import pytest

if __name__ == "__main__":
    # 运行test_cases目录下的所有用例,生成HTML报告到reports目录
    pytest.main([
        "test_cases/",  # 用例目录
        "-v",  # 显示详细日志
        "--html=reports/login_test_report.html",  # 生成pytest-html报告
        "--self-contained-html",  # 报告独立(不依赖外部文件)
        "--maxfail=3"  # 失败3条用例后停止执行(可选)
    ])

五、运行与结果查看

  1. 执行测试:运行 run.py,控制台会输出用例执行进度(如 collected 4 items 表示4条用例)。
  2. 查看报告:打开 reports/login_test_report.html,可看到每条用例的执行结果(通过/失败)、失败原因(含断言信息)。
  3. AirTest日志:默认在 test_cases 目录生成 log 文件夹,包含每条操作的截图和日志,便于调试失败用例。

六、关键优化技巧

  1. 控件定位稳定性

    • 优先用 id(唯一且不变),其次用 text(需确保文字固定),避免用坐标(受屏幕分辨率影响)。
    • wait_for_appearance(timeout=10) 替代固定 sleep(),减少等待时间的同时避免控件未加载导致的失败。
  2. 数据驱动扩展

    • 支持从JSON/CSV/数据库读取数据,只需在 data/test_data.py 中修改数据读取逻辑。
  3. 报告增强

    • 结合 pytest-rerunfailures 实现失败重试(pip install pytest-rerunfailures,运行时加 --reruns 2)。
    • AirTestsimple_report 生成操作截图报告(在 teardown_class 中添加:simple_report(__file__, output="reports/airtest_report.html"))。

通过这套框架,既能利用 AirTest 高效操作UI,又能通过 ddt 覆盖多场景,同时用 Pytest 简化用例管理,适合移动端APP的UI自动化测试。核心是分离数据、控件、逻辑,让用例更易维护。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐