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文件
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'
定义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
设置全局启动入口
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
...
获取配置文件参数固定方法,结合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}")
定义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)
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