pytest+yaml+appium实现App-UI自动化测试

本文介绍了如何结合pytest、yaml和appium实现App的UI自动化测试。项目结构包括common、config、outputs、reports、pageobjects、testcase等文件夹,以及conftest.py、pytest.ini和requirements.txt等文件。common文件夹中包含了appium操作、页面元素基础操作、yaml文件读写等方法。config文件夹存储配置参数,pageobjects用于封装测试方法,testcase存放测试用例。conftest.py用于定义钩子和共享夹具,pytest.ini配置pytest行为,requirements.txt列出项目依赖。
摘要由CSDN通过智能技术生成

一、项目整体结构

common:用于存放一些公共方法文件;

config:用于存放相关配置文件;

outputs:用于存放log及测试报告文件;

pageobjects:用于存放测试的相关方法封装文件;

testcase:用于存放测试用例文件;

二、common文件夹

common文件中目前主要存放了4个基础方法文件,本别是项目相关路径、被测app相关信息、页面元素基础操作方法、yaml文件读取写入方法文件,以下分别介绍具体内容。

1.all_path.py

此文件主要用于配置相关文件的读取路径,包括:log文件存储路径、测试报告输出路径、图片存储路径等。

import os

base_path = os.path.dirname(os.path.dirname(__file__))  # 获取当前文件所在目录的上两级路径即项目根目录

appPath = os.path.join(base_path, 'app')
appium_server_logPath = os.path.join(base_path, 'Outputs', 'appium_log.log')
configPath = os.path.join(base_path, 'Config')
outputsPath = os.path.join(base_path, 'Outputs')
logPath = os.path.join(base_path, 'Outputs', 'logs')
picturePath = os.path.join(base_path, 'Outputs', 'picture')
reportsPath = os.path.join(base_path, 'Outputs', 'reports')

2.app_info.py

存放cmd命令操作app方法,当前项目中暂未使用待后续研究熟悉后可使用该文件封装的方法进行自动启动、停止appium服务及操作app。

import subprocess
import logging
import os
from Common.all_path import appPath


def exec_cmd(cmd) -> str:
    result = os.popen(cmd).read()
    return result


def get_app_name(file_dir) -> str:
    for root, dirs, files in os.walk(file_dir):
        files = [file for file in files if file.endswith(".apk")]
        if len(files) == 1:
            return files[0]
        else:
            raise FileNotFoundError("{}目录下没有测试包或者存在多个测试包 ".format(file_dir))


def get_app_package_name() -> str:
    cmd = "aapt dump badging {} | findstr package".format(os.path.join(appPath, get_app_name(appPath)))
    result = exec_cmd(cmd)
    if "package" in result:
        package_name = result.strip().split(" ")[1].split('=')[1]
        return package_name
    else:
        raise NameError("未获取到package name")


def get_app_launchable_activity() -> str:
    cmd = "aapt dump badging {} | findstr launchable".format(os.path.join(appPath, get_app_name(appPath)))
    result = exec_cmd(cmd)
    if "launchable" in result:
        launchable_activity = result.strip().split(" ")[1].split('=')[1].replace("label", '')
        return launchable_activity
    else:
        raise NameError("未获取到launchable activity")


def get_devices_version(device: str) -> str:
    if not isinstance(device, str):
        raise Exception("device type is should str..")
    result = exec_cmd("adb -s {} shell getprop ro.build.version.release".format(device))
    result = result.strip()
    if "error" not in result:
        return result
    else:
        raise Exception("获取设备系统版本失败,无法进行正常测试")


def get_all_devices() -> list:
    result = exec_cmd('adb devices')
    result = result.strip().split(" ")[3].replace("\n", '').replace("\t", ''). \
        replace("attached", '').split('device')
    result.remove('')
    if len(result) == 0:
        raise Exception("电脑未连接设备信息,无法进行正常测试")
    return result


def get_device_infos():
    """ [{'platform_version': '8.1.0', 'server_port': 4723, 'system_port': 8200, 'device': 'HDP9K18629901709'}] """
    device_infos = []
    devices = get_all_devices()
    for i in range(len(devices)):
        device_dict = {"platform_version": get_devices_version(devices[i]), "server_port": 4723 + i * 2,
                       "system_port": 8200 + i * 1, "device": devices[i]}
        device_infos.append(device_dict)

    if len(device_infos) < 1:
        raise Exception("当前电脑未连接设备信息。。。")

    return device_infos


def uninstall_app(device_list: list) -> None:
    """ 卸载 app 命令:adb -s 127.0.0.1:HDP9K18629901709 uninstall "com.xkw.client """
    if not isinstance(device_list, list):
        raise Exception("device_list is should list!")

    for device_info in device_list:
        cmd = 'adb -s {} uninstall "{}"'.format(device_info.get("device").split(':')[-1],
                                                          str(get_app_package_name())).replace("'", '')
        logging.info("开始卸载设备上应用:{}".format(cmd))
        exec_cmd(cmd)


def install_app():
    """ 下载app """
    subprocess.Popen('chcp 65001', shell=True)
    cmd = 'adb install {}'.format(os.path.join(appPath, get_app_name(appPath)))
    logging.info("开始下载设备上应用:{}".format(cmd))
    subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE)


def check_kill_appium(port):
    """ 备选方案 杀掉appium 在端口只有1个的时候作用 """
    cmd = 'netstat -o -n -a | findstr {}'.format(port['server_port'])
    result = exec_cmd(cmd)
    if "LISTENING" in result:
        package_name = result.strip().split(" ")[-1]
        cmds = 'taskkill /f /pid {}'.format(package_name)
        result = exec_cmd(cmds)
        if 'PID' in result:
            logging.info('释放端口: {} 成功'.format(result))
            return result
        else:
            raise NameError("未成功: 终止 PID 为 {}  的进程".format(result))
    else:
        raise NameError("appium服务未开启")


def kill_app():
    """ 备选方案 杀掉appium 在端口只有1个的时候作用  """
    device_dict = get_device_infos()[-1]
    cmd = 'netstat -o -n -a | findstr {}'.format(device_dict['server_port'])
    result = exec_cmd(cmd)
    if "LISTENING" in result:
        cmds = 'taskkill /f /t /im node.exe'
        result = exec_cmd(cmds)
        if 'PID' in result:
            return result
        else:
            raise NameError("未成功: 终止 PID 为 {}  的进程".format(result))
    else:
        raise NameError("appium服务未开启")

3.base.py

base文件主要存放已经封装好的一些页面元素操作方法,包括:查找元素、点击元素、获取元素文本等...

import time

import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from appium.webdriver.common.touch_action import TouchAction
from Common.all_path import picturePath
import datetime
import os
import logging


class Base:

    @classmethod
    # 自定义一个元素查找方法
    def find_element(cls, driver, feature, timeout=5, poll=1.0):
        # feature = By.XPATH,"//*[@text='显示']"
        """
        依据用户传入的元素信息特征,然后返回当前用户想要查找元素
        :param poll: 可选参数,等待元素可见的轮询间隔时间,默认为 1.0 秒。
        :param timeout: 可选参数,等待元素可见的最长时间,默认为 5秒。
        :param driver:
        :param feature: 字典类型,包含用户希望的查找方式,及该方式对应的值
        :return: 返回当前用户查找的元素
        """
        try:
            by = feature['type']
            value = feature['value']
            doc = feature['doc']
            logging.info("页面开始查找元素:{},定位类型:{},值:{}".format(doc, by, value))
            wait = WebDriverWait(driver, timeout, poll)
            if by == 'XPATH':
                loc = wait.until(lambda x: x.find_element(By.XPATH, value))
            elif by == 'ID':
                loc = wait.until(lambda x: x.find_element(By.ID, value))
            elif by == 'CLASS_N
自动化测试是一种通过编写代码来执行测试用例的方法,它可以提高测试效率和准确性。在Python中,结合使用pytestyaml可以实现自动化测试。 首先,你需要安装pytest和PyYAML库。你可以使用以下命令来安装它们: ``` pip install pytest PyYAML ``` 接下来,你可以创建一个测试用例文件,使用pytest的装饰器来标记测试函数。例如,你可以创建一个名为`test_example.py`的文件,并在其中定义一个测试函数: ```python import pytest def test_addition(): assert 2 + 2 == 4 def test_subtraction(): assert 5 - 3 == 2 ``` 然后,你可以使用以下命令来运行测试: ``` pytest test_example.py ``` 接下来,你可以使用yaml文件来定义测试数据和预期结果。例如,你可以创建一个名为`test_data.yaml`的文件,并在其中定义测试数据和预期结果: ```yaml - data: [2, 2] expected_result: 4 - data: [5, 3] expected_result: 2 ``` 然后,在测试函数中读取yaml文件,并使用其中的数据运行测试。你可以使用PyYAML库来解析yaml文件。例如,修改`test_example.py`文件如下: ```python import pytest import yaml def load_test_data(): with open('test_data.yaml', 'r') as file: test_data = yaml.safe_load(file) return test_data @pytest.mark.parametrize("data, expected_result", load_test_data()) def test_addition(data, expected_result): assert data[0] + data[1] == expected_result @pytest.mark.parametrize("data, expected_result", load_test_data()) def test_subtraction(data, expected_result): assert data[0] - data[1] == expected_result ``` 运行pytest命令时,它会自动加载yaml文件中的测试数据,并运行相应的测试用例。 这只是一个简单的例子,你可以根据实际需求来编写更复杂的自动化测试代码。希望对你有帮助!如有其他问题,请继续提问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paul.Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值