【关键字驱动】实现基于Python的关键字驱动自动化测试框架

5 篇文章 0 订阅


测试用例模板
映射关系

框架结构

pythonProject
    │  config.ini  #全局配置文件
    │
    ├─action
    │      pageAction.py  #解析excel测试用例中的前置条件关键字、测试步骤关键字和预期结果关键字
    │
    ├─driver
    │      android_driver.py
    │      app.py
    │
    ├─src
    │  └─util
    │      config.py
    │      readExcel.py
    │      UIObject.py  
    │      loader.py
    │
    ├─tools  #
    └─UI     #Qt designer生成的UI文件

action

import copy
import json
import random
import time

from src.util.loder import EnvLoader
from src.util.logger import logger

func_res = ""


class PageAction:
    def __init__(self, excel_data, report_path):
        self.testcase_df = excel_data
        self.testcase_dict = excel_data.to_dict()
        # 测试报告路径
        self.report_path = report_path
        # 测试结果
        self.test_report = {}
        # 加载关键字对应表
        self.keyword_dict = self.loading_keyword_dict()
        # 测试步骤返回结果
        self.func_res = ""

    def loading_keyword_dict(self) -> dict:
        """
        加载关键字对应表
        :return:
        """
        try:
            with open(f"{self.report_path}/data.json", "r+", encoding="utf8") as file:
                data = json.load(file)
                return data
        except Exception as e:
            logger.error(e)
            return {}

    @classmethod
    def base_method(cls, keyword_info, remark="测试步骤"):
        """
        构造执行函数
        :param keyword_info:
        :param remark:
        :return:
        """
        if isinstance(keyword_info, dict):
            try:
                keyword = keyword_info.get('keyword', '')
                parameter = keyword_info.get('parameter', '')
                if parameter:
                    func = f"{keyword}('{parameter}')"
                else:
                    func = f"{keyword}()"
                func_res = eval(func)
                logger.info(f"{remark} {func},返回值:{func_res}")
                return func_res
            except Exception as e:
                logger.error(e)
                return None

    def switch_precondition(self, index: int):
        """
        转换前置条件
        :param index:
        :return:
        """
        # 前置条件关键字
        index_precondition_key = self.testcase_dict.get("前置条件关键字", {}).get(index, '')
        if index_precondition_key:
            for keyword_name in index_precondition_key.split('\n'):
                # 关键字默认前N位是“序号.”
                keyword_name = keyword_name.split('.')[1]
                # 排除空行
                if keyword_name:
                    try:
                        keyword_info = self.keyword_dict.get(keyword_name)
                        if isinstance(keyword_info, list):
                            # list中 N选1
                            select_info = random.sample(keyword_info, 1)
                            self.base_method(select_info)
                        elif isinstance(keyword_info, dict):
                            self.base_method(keyword_info)
                        else:
                            pass
                    except Exception as e:
                        logger.error(e)

    def eval_test_procedures_and_expect_result(self, code, test_key, expect_key):
        """
        拼接测试步骤及对应的期望结果
        :param code:
        :param test_key:
        :param expect_key:
        :return:
        """
        global func_res
        func_res = self.base_method(test_key, remark=f"测试步骤{code}")
        # 执行断言
        if expect_key:
            for item in expect_key.split('\n'):
                # 拆分单元格,分成多行,item:1.open_app
                if code in item:
                    # 寻找与操作步骤对应的期望结果
                    for expect_name_item in item.split(","):
                        # 兼容一个测试步骤对应多个期望结果
                        expect_data = self.keyword_dict.get(expect_name_item.split('.')[1])
                        assert_res = self.base_method(expect_data, remark=f"断言{code}")
                        return f"{code}.{assert_res}"

    def switch_test_procedures(self, index: int):
        """
        转换测试步骤及对应的期望结果
        :param index:
        :return:
        """
        index_procedures_key = self.testcase_dict.get("测试步骤关键字", {}).get(index, '')
        index_expect_key = self.testcase_dict.get("预期结果关键字", {}).get(index, '')
        if index_procedures_key:
            try:
                # 断言结果
                assert_res_list = []
                for keyword_name in index_procedures_key.split('\n'):
                    if keyword_name:
                        # 排除keyword_name为空的情况
                        batch_keyword = keyword_name.split('.')
                        # 测试步骤编号
                        code = batch_keyword[0]
                        # 关键字
                        keyword_name = batch_keyword[1]
                        # 排除空行
                        if keyword_name:
                            keyword_info = self.keyword_dict.get(keyword_name)
                            if isinstance(keyword_info, list):
                                # list中 N选1
                                select_info = random.sample(keyword_info, 1)
                                assert_res = self.eval_test_procedures_and_expect_result(code, select_info,
                                                                                         index_expect_key)
                                if assert_res:
                                    assert_res_list.append(assert_res)
                            elif isinstance(keyword_info, dict):
                                assert_res = self.eval_test_procedures_and_expect_result(code, keyword_info,
                                                                                         index_expect_key)
                                if assert_res:
                                    assert_res_list.append(assert_res)
                            else:
                                pass
                return assert_res_list
            except Exception as e:
                logger.error(e)
                return []

    def test_case(self):

        test_num_dict = self.testcase_dict.get("用例编号", {})
        for index, test_code in test_num_dict.items():
            try:
                # 获取结束标志
                end_flag = int(EnvLoader.get_os_environ("end_flag"))
                if not end_flag:
                    while True:
                        time.sleep(2)
                        end_flag2 = int(EnvLoader.get_os_environ("end_flag"))
                        pause_flag = int(EnvLoader.get_os_environ("pause_flag"))
                        if not pause_flag:
                            logger.info(f"==========开始执行[{test_code}]==========")
                            # 前置条件
                            self.switch_precondition(index)
                            # 测试步骤及期望结果
                            assert_res_list = self.switch_test_procedures(index)
                            # 记录测试结果
                            self.test_report[str(test_code)] = '\n'.join(assert_res_list)
                            # 写入excel
                            temp_df = copy.deepcopy(self.testcase_df)
                            test_res = temp_df.apply(lambda x: self.test_report.get(x.test_code), axis=1)
                            temp_df.loc[:, "自动化测试结果"] = test_res
                            temp_df.to_excel(f"{self.report_path}/测试报告.xlsx", sheet_name="Sheet1", index=False)
                            # 用于前端多线程展示数据标记
                            EnvLoader.set_os_environ({"finished_testcase_num": str(len(self.test_report.keys()))})
                        elif end_flag2:
                            break
                        else:
                            pass
                else:
                    break
            except Exception as e:
                logger.errpr(e)
            finally:
                ...
        logger.info(f"==========测试结束==========")
        return '1'



driver

android_driver.py

定义Android启动机制

import os
from appium import webdriver
from appium.webdriver.webdriver import WebDriver

from src.util.config import Config


class Android:
    driver: WebDriver = None
    device_typ = Config.get('Device', 'device_type')
    app_package = Config.get('Device', 'app_package')
    app_activity = Config.get('Device', 'app_activity')

    @classmethod
    def get_device_version(cls):
        """
        获取设备号
        :return: 
        """
        result = os.popen("adb devices").readlines()
        return result[1].split('\t')[0]

    @classmethod
    def get_devices_android_version(cls):
        """
        获取设备的android版本号
        :return: 
        """
        result = os.popen("adb shell getprop ro.build.version.release").readlines()
        return result[0].replace('\n', '')

    @classmethod
    def start(cls):
        if cls.driver is None:
            caps = {
                'platformName': cls.device_typ,
                'platformVersion': cls.get_devices_android_version(),
                'devicesName': cls.get_device_version(),
                'appPackage': cls.app_package,
                'appActivity': cls.app_activity,
                'automationName': 'UIAutomator2',
                'noReset': 'true',
                'settings[waitForIdleTimeout]': 100
            }
            cls.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)
            cls.driver.implicitly_wait(10)
        else:
            cls.driver.launch_app()
        return cls

app.py

设置全局启动入口

from appium.webdriver.webdriver import WebDriver
from driver.android_driver import Android
from src.util.config import Config


class APP:
    driver: WebDriver = None
    device_typ = Config.get('Device', 'device_type')

    @classmethod
    def tearDownClass(cls):
        if cls.driver:
            cls.driver.quit()

    @classmethod
    def start(cls):
        if 'Android' in cls.device_typ:
            Android().start()
            cls.driver = Android.driver
        elif cls.device_typ.lower() == 'ios':
            # todo
            ...

src

config.py

获取配置文件参数固定方法,结合config.ini文件

[Base]
history_path = C:/tmp/

[Env]
run_driver = win

[Device]
device_type = Android
app_package = com.android.xxx
app_activity = com.android.xxx.xxx

[Log]
level = info
import configparser
import sys
import os


def base_path(relative_path=None):
    if getattr(sys, 'frozen', None):
        # exe执行时获取路径的方法
        base_dir = sys._MEIPASS
    else:
        # 本地调试获取路径方法
        base_dir = os.path.dirname(os.path.abspath(__file__))
        base_dir = f"{base_dir.split('项目名称')[0]}项目名称"
    if relative_path:
        return os.path.join(base_dir, relative_path)
    else:
        return base_dir


class Config:
    project_path = base_path()
    project_path = project_path.replace('\\', '/')
    if project_path[-1] != '/':
        project_path = project_path + '/'

    _config_path = r"{}config.ini".format(project_path)

    _config = configparser.ConfigParser()
    _config.read(_config_path, encoding='utf-8')

    @classmethod
    def get(cls, section, option):
        try:
            return cls._config.get(section, option)
        except configparser.NoSectionError:
            raise Exception(f"Config.ini文件读取传参异常,无此Section:{section}")
        except configparser.NoOptionError:
            raise Exception(f"Config.ini文件读取传参异常,无此Option:{option}")
        except Exception as e:
            raise Exception(e)

    @classmethod
    def get_section(cls, section):
        try:
            return dict(cls._config.items(section=section))
        except configparser.NoSectionError:
            raise Exception(f"Config.ini文件读取传参异常,无此Section:{section}")

UIObject.py

定义UI自动化需要的通用方法

import os
import time

from driver.app import App
from appium.webdriver.webdriver import WebDriver

driver: WebDriver = None


def start_app():
    global driver
    app = App()
    app.start()
    driver = app.driver


def open_app(by: str):
    os.popen(f"adb shell am start {by}")
    time.sleep(2)

loader.py

import os


class EnvLoader:

    @staticmethod
    def set_os_environ(variables_mapping: dict):
        for variable in variables_mapping:
            os.environ[variable] = variables_mapping[variable]

    @staticmethod
    def get_os_environ(variable_name):
        try:
            return os.environ[variable_name]
        except KeyError:
            return None

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值