1. 优化run.py文件代码 2. 在conftest.py中添加pytest_terminal_summary获取测试运行情况并打印在日志文件中

This commit is contained in:
floraachy
2023-06-26 11:21:27 +08:00
parent c24e67e3cb
commit b98d1da094
4 changed files with 239 additions and 133 deletions

View File

@@ -75,7 +75,7 @@ def zip_file(in_path: str, out_path: str):
"""
# 如果传入的路径是一个目录才进行压缩操作
if os.path.isdir(in_path):
print("目标路径是一个目录,开始进行压缩......")
print(f"目标路径:{in_path} 是一个目录,开始进行压缩......")
# 写入
zip = zipfile.ZipFile(out_path, "w", zipfile.ZIP_DEFLATED)
for path, dirnames, filenames in os.walk(in_path):
@@ -87,9 +87,9 @@ def zip_file(in_path: str, out_path: str):
path, filename), os.path.join(
fpath, filename))
zip.close()
print("压缩完成!")
print(f"目标路径:{in_path} 压缩完成!, 压缩文件路径:{out_path}")
else:
print("目标路径不是一个目录,请检查!")
print(f"目标路径:{in_path} 不是一个目录,请检查!")
def delete_dir_file(file_path):
@@ -99,7 +99,7 @@ def delete_dir_file(file_path):
"""
paths = os.listdir(file_path)
if paths:
print("目标目录存在文件或目录,进行删除操作")
print(f"目标目录: {file_path} 存在文件或目录,进行删除操作")
for item in paths:
path = os.path.join(file_path, item)
# 如果目标路径是一个文件使用os.remove删除
@@ -109,7 +109,7 @@ def delete_dir_file(file_path):
if os.path.isdir(path):
os.rmdir(path)
else:
print("目标目录不存在文件或目录,不需要删除")
print(f"目标目录: {file_path} 不存在文件或目录,不需要删除")
def copy_file(src_file_path, dest_dir_path):

View File

@@ -5,36 +5,17 @@
# @File : conftest.py
# @Software: PyCharm
# @Desc: 这是文件的描述信息
import os.path
from config.global_vars import ENV_VARS, GLOBAL_VARS
import pytest
from py._xmlgen import html # 安装pytest-html版本最好是2.1.1
import time
from time import strftime
import re
import pytest
from py._xmlgen import html # 安装pytest-html版本最好是2.1.1
from config.global_vars import ENV_VARS, GLOBAL_VARS
from loguru import logger
# ------------------------------------- START: 报告处理 ---------------------------------------#
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
"""设置列"用例描述"的值为用例的标题title"""
outcome = yield
# 获取调用结果的测试报告返回一个report对象
# report对象的属性包括whensteup, call, teardown三个值、nodeid(测试用例的名字)、outcome(用例的执行结果passed,failed)
report = outcome.get_result()
# 将测试用例的title作为测试报告"用例描述"列的值。
# 注意参数传递时需要这样写:@pytest.mark.parametrize("case", cases, ids=["{}".format(case["title"]) for case in cases])
report.description = re.findall('\[(.*?)\]', report.nodeid)[0]
report.func = report.nodeid.split("[")[0]
def pytest_html_report_title(report):
"""
修改报告标题
"""
report.title = f'{ENV_VARS["common"]["project_name"]} {ENV_VARS["common"]["report_title"]}'
# ------------------------------------- START: pytest钩子函数处理---------------------------------------#
def pytest_configure(config):
"""
# 在测试运行前修改Environment部分信息配置测试报告环境信息
@@ -45,6 +26,10 @@ def pytest_configure(config):
# 给环境表 移除packages 及plugins
config._metadata.pop("Packages")
config._metadata.pop("Plugins")
# 向pytest的配置中添加marker
# TODO 暂时还没给用例添加
config.addinivalue_line("markers", 'smoke')
config.addinivalue_line("markers", '回归测试')
@pytest.hookimpl(tryfirst=True)
@@ -56,6 +41,74 @@ def pytest_sessionfinish(session, exitstatus):
session.config._metadata['项目环境'] = GLOBAL_VARS.get("host", "")
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
"""设置列"用例描述"的值为用例的标题title"""
outcome = yield
# 获取调用结果的测试报告返回一个report对象
# report对象的属性包括whensteup, call, teardown三个值、nodeid(测试用例的名字)、outcome(用例的执行结果passed,failed)
report = outcome.get_result()
# 将测试用例的title作为测试报告"用例描述"列的值。
# 注意参数传递时需要这样写:@pytest.mark.parametrize("case", cases, ids=["{}".format(case["title"]) for case in cases])
report.description = re.findall('\\[(.*?)\\]', report.nodeid)[0]
report.func = report.nodeid.split("[")[0]
def pytest_terminal_summary(terminalreporter, config):
"""
收集测试结果
"""
_RERUN = len([i for i in terminalreporter.stats.get('rerun', []) if i.when != 'teardown'])
try:
# 获取pytest传参--reruns的值
reruns_value = int(config.getoption("--reruns"))
_RERUN = int(_RERUN / reruns_value)
except Exception:
reruns_value = "未配置--reruns参数"
_RERUN = len([i for i in terminalreporter.stats.get('rerun', []) if i.when != 'teardown'])
_PASSED = len([i for i in terminalreporter.stats.get('passed', []) if i.when != 'teardown'])
_ERROR = len([i for i in terminalreporter.stats.get('error', []) if i.when != 'teardown'])
_FAILED = len([i for i in terminalreporter.stats.get('failed', []) if i.when != 'teardown'])
_SKIPPED = len([i for i in terminalreporter.stats.get('skipped', []) if i.when != 'teardown'])
_XPASSED = len([i for i in terminalreporter.stats.get('xpassed', []) if i.when != 'teardown'])
_XFAILED = len([i for i in terminalreporter.stats.get('xfailed', []) if i.when != 'teardown'])
_TOTAL = terminalreporter._numcollected
_TIMES = time.time() - terminalreporter._sessionstarttime
logger.info(f"\n======================================================\n"
"-------------测试结果--------------------\n"
f"用例总数: {_TOTAL}\n"
f"跳过用例数: {_SKIPPED}\n"
f"实际执行用例总数: {_TOTAL - _SKIPPED}\n\n"
f"异常用例数: {_ERROR}\n"
f"失败用例数: {_FAILED}\n"
f"重跑的用例数 || 重跑次数: {_RERUN} || {reruns_value}\n"
f"意外通过的用例数: {_XPASSED}\n"
f"预期失败的用例数: {_XFAILED}\n\n"
"用例执行时长: %.2f" % _TIMES + " s\n")
try:
_RATE = _PASSED / (_TOTAL - _SKIPPED) * 100
logger.info(
f"\n用例成功率: %.2f" % _RATE + " %\n"
"=====================================================")
except ZeroDivisionError:
logger.info(
f"用例成功率: 0.00 %\n"
"=====================================================")
# ------------------------------------- END: pytest钩子函数处理---------------------------------------#
# ------------------------------------- START: pytest-html钩子函数处理 ---------------------------------------#
def pytest_html_report_title(report):
"""
修改报告标题
"""
report.title = f'{ENV_VARS["common"]["project_name"]} {ENV_VARS["common"]["report_title"]}'
def pytest_html_results_summary(prefix, summary, postfix):
"""
修改Summary部分的信息
@@ -97,4 +150,4 @@ def pytest_html_results_table_html(report, data):
del data[:]
data.append(html.div("这条用例通过啦!", class_="empty log"))
# ------------------------------------- END: 报告处理 ---------------------------------------#
# ------------------------------------- END: pytest-html钩子函数处理 ---------------------------------------#

View File

@@ -1,11 +1,14 @@
[pytest]
# cmd params
addopts = -s --cache-clear --capture=sys --self-contained-html --reruns=0 --reruns-delay=0
addopts =
-s
--cache-clear
--capture=sys
--self-contained-html
--reruns=1
--reruns-delay=5
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
markers =
test_login_demo: login
test_gitlink_demo_dlmkexcel: login excel
test_gitlink_demo_xjxmmkexcel: new project excel
test_new_project_demo: new_project
auto: auto generate case
test_demo: demo case

246
run.py
View File

@@ -13,6 +13,22 @@
> python run.py -env live 在live环境运行测试用例
> python run.py -env=test 在test环境运行测试用例
> python run.py -report=pytest-html (默认在test环境运行测试用例, 报告采用pytest-html)
pytest相关参数以下也可通过pytest.ini配置
--reruns: 失败重跑次数
--reruns-delay 失败重跑间隔时间
--count: 重复执行次数
-v: 显示错误位置以及错误的详细信息
-s: 等价于 pytest --capture=no 可以捕获print函数的输出
-q: 简化输出信息
-m: 运行指定标签的测试用例
-x: 一旦错误,则停止运行
--cache-clear 清除pytest的缓存包括测试结果缓存、抓取的fixture实例缓存和收集器信息缓存等
--maxfail: 设置最大失败次数,当超出这个阈值时,则不会在执行测试用例
"--reruns=3", "--reruns-delay=2"
allure相关参数
-alluredir这个选项用于指定存储测试结果的路径
"""
import os
@@ -32,120 +48,154 @@ from config.global_vars import GLOBAL_VARS, ENV_VARS
from common_utils.files_handle import zip_file, copy_file
def capture_all_logs(level=LOG_LEVEL):
logger.info("""
_ _ _ _____ _
__ _ _ __ (_) / \\ _ _| |_ __|_ _|__ ___| |_
/ _` | "_ \\| | / _ \\| | | | __/ _ \\| |/ _ \\/ __| __|
| (_| | |_) | |/ ___ \\ |_| | || (_) | | __/\\__ \\ |_
\\__,_| .__/|_/_/ \\_\\__,_|\\__\\___/|_|\\___||___/\\__|
|_|
Starting ... ... ...
""")
if level:
# 仅捕获指定级别日志
logger.add(
os.path.join(LOG_DIR, "runtime_{time}.log"),
enqueue=True,
encoding="utf-8",
rotation="00:00",
level=LOG_LEVEL.upper(),
format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}",
)
else:
# 捕获所有日志
logger.add(
os.path.join(LOG_DIR, "runtime_{time}_{level}.log"),
enqueue=True,
encoding="utf-8",
rotation="00:00",
format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}",
)
# 封装生成测试用例的函数
def auto_generate_test_cases():
# 删除原有的测试用例,以便生成新的测试用例
if os.path.exists(AUTO_CASE_DIR):
shutil.rmtree(AUTO_CASE_DIR)
# 根据data里面的yaml/excel文件自动生成测试用例
generate_cases()
# 封装执行 pytest 的函数
def run_pytest(report_type, mark_param):
arg_list = []
# 执行指定的测试用例
if mark_param is not None:
arg_list.append(f"-m {mark_param}")
current_time = datetime.now().strftime("%Y-%m-%d+%H_%M_%S")
# 生成 Allure 报告
if report_type.lower() == "allure":
arg_list.extend(
[
"-q",
"--cache-clear",
f'--alluredir={ALLURE_RESULTS_DIR}',
"--clean-alluredir",
]
)
pytest.main(args=arg_list)
# ------------------------ 使用allure生成测试报告 ------------------------
# 从LIB_DIR目录中寻找以allure开头的目录作为allure模块的目录并进入bin目录下
allure_path = os.path.join(LIB_DIR, [i for i in os.listdir(LIB_DIR) if i.startswith("allure")][0], "bin")
# 根据windows或linux环境判断, 执行指定的命令。
cmd = PlatformHandle().allure[1].format(
os.path.join(allure_path, PlatformHandle().allure[0]),
ALLURE_RESULTS_DIR,
ALLURE_HTML_DIR,
)
os.popen(cmd).read()
# ------------------------ 美化allure测试报告 ------------------------
# 设置allure报告窗口标题
AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_windows_title(
new_title=ENV_VARS["common"]["project_name"]
)
# 设置allure报告名称
AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_report_name(
new_name=ENV_VARS["common"]["report_title"]
)
# 往allure测试报告中写入环境配置相关信息
env_info = ENV_VARS["common"]
env_info["run_env"] = GLOBAL_VARS.get("host", "")
AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_report_env_on_html(
env_info=env_info
)
# 复制http_server.exe以及双击查看报告.bat文件到allure-html根目录下用于支撑电脑在未安装allure服务的情况下打开allure-html报告
# 注意ZIP文件的名称包含某些特殊字符会导致无法使用.bat文件打开allure-html报告 例如空格,/ 等
allure_config_path = os.path.join(CONF_DIR, "allure_config")
copy_file(src_file_path=os.path.join(allure_config_path,
[i for i in os.listdir(allure_config_path) if i.endswith(".exe")][0]),
dest_dir_path=ALLURE_HTML_DIR)
copy_file(src_file_path=os.path.join(allure_config_path,
[i for i in os.listdir(allure_config_path) if i.endswith(".bat")][0]),
dest_dir_path=ALLURE_HTML_DIR)
# ------------------------ allure测试报告生成完毕压缩allure测试报告为ZIP文件 ------------------------
# report_path以及attachment_path后面发送测试结果需要用到
report_path = ALLURE_HTML_DIR
attachment_path = os.path.join(REPORT_DIR, f'autotest_{str(current_time)}.zip')
# 压缩allure-html报告为一个压缩文件zip
zip_file(in_path=report_path, out_path=attachment_path)
# 生成 pytest-html 报告
else:
report_path = os.path.join(REPORT_DIR, "autotest_" + str(current_time) + ".html")
attachment_path = report_path
pytest_html_config_path = os.path.join(CONF_DIR, "pytest_html_config")
report_css = os.path.join(pytest_html_config_path, "pytest_html_report.css")
arg_list.extend([f"--html={report_path}", f"--css={report_css}"])
pytest.main(args=arg_list)
return report_path, attachment_path
# 主函数
@click.command()
@click.option("-report", default="allure", help="选择需要生成的测试报告pytest-html, allure")
@click.option("-env", default="test", help="输入运行环境test 或 live")
@click.option("-m", default=None, help="选择需要运行的用例python.ini配置的名称")
def run(env, m, report):
def run(report, env, m):
try:
# ------------------------ 捕获日志 ------------------------
# 捕获所有日志
logger.add(os.path.join(LOG_DIR, "runtime_{time}_all.log"), enqueue=True, encoding="utf-8", rotation="00:00",
format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}")
# # 仅捕获指定级别日志
# logger.add(os.path.join(LOG_DIR, "runtime_{time}.log"), enqueue=True, encoding="utf-8", rotation="00:00",
# level=LOG_LEVEL.upper(),
# format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}")
logger.info("""
_ _ _ _____ _
__ _ _ __ (_) / \\ _ _| |_ __|_ _|__ ___| |_
/ _` | "_ \\| | / _ \\| | | | __/ _ \\| |/ _ \\/ __| __|
| (_| | |_) | |/ ___ \\ |_| | || (_) | | __/\\__ \\ |_
\\__,_| .__/|_/_/ \\_\\__,_|\\__\\___/|_|\\___||___/\\__|
|_|
Starting ... ... ...
""")
# ------------------------ 捕获日志----------------------------
capture_all_logs()
# ------------------------ 设置全局变量 ------------------------
# 根据指定的环境参数将运行环境所需相关配置数据保存到GLOBAL_VARS
GLOBAL_VARS["env_key"] = env.lower()
if ENV_VARS.get(env.lower(), None):
for k, v in ENV_VARS[env.lower()].items():
GLOBAL_VARS[k] = v
# ------------------------ 自动生成测试用例 ------------------------
# 删除原有的测试用例,以便生成新的测试用例
if os.path.exists(AUTO_CASE_DIR):
# 删除文件夹
shutil.rmtree(AUTO_CASE_DIR)
if ENV_VARS.get(env.lower()):
GLOBAL_VARS.update(ENV_VARS[env.lower()])
# 根据data里面的yaml/excel文件自动生成测试用例
generate_cases()
# ------------------------ 自动生成测试用例 ------------------------
auto_generate_test_cases()
# ------------------------ pytest执行测试用例 ------------------------
"""
pytest相关参数以下也可通过pytest.ini配置
--reruns: 失败重跑次数
--reruns-delay 失败重跑间隔时间
--count: 重复执行次数
-v: 显示错误位置以及错误的详细信息
-s: 等价于 pytest --capture=no 可以捕获print函数的输出
-q: 简化输出信息
-m: 运行指定标签的测试用例
-x: 一旦错误,则停止运行
--cache-clear 清除pytest的缓存包括测试结果缓存、抓取的fixture实例缓存和收集器信息缓存等
--maxfail: 设置最大失败次数,当超出这个阈值时,则不会在执行测试用例
"--reruns=3", "--reruns-delay=2"
"""
arg_list = []
# 执行指定测试用例
if m is not None:
arg_list.append(f"-m {m}")
current_time = datetime.now().strftime("%Y-%m-%d+%H_%M_%S")
if report.lower() == "allure":
arg_list.extend(['-q', '--cache-clear', f'--alluredir={ALLURE_RESULTS_DIR}', '--clean-alluredir'])
"""
allure相关参数
-alluredir这个选项用于指定存储测试结果的路径
"""
pytest.main(args=arg_list)
# ------------------------ 使用allure生成测试报告 ------------------------
logger.debug("-------开始生成allure测试报告-------")
plat = PlatformHandle()
# 从LIB_DIR目录中寻找以allure开头的目录作为allure模块的目录并进入bin目录下
allure_path = os.path.join(LIB_DIR, [i for i in os.listdir(LIB_DIR) if i.startswith("allure")][0], "bin")
# 根据windows或linux环境判断, 执行指定的命令。plat.allure[0]=cmd, plat.allure[1]=cmd2
cmd = plat.allure[1].format(os.path.join(allure_path, plat.allure[0]), ALLURE_RESULTS_DIR, ALLURE_HTML_DIR)
# 执行命令行命令并通过read()方法将命令的结果返回os.popen() 方法用于从一个命令打开一个管道。在UnixWindows中有效
os.popen(cmd).read()
logger.debug("-------美化allure测试报告-------")
AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_windows_title(
new_title=ENV_VARS["common"]["project_name"])
AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_report_name(
new_name=ENV_VARS["common"]["report_title"])
logger.debug("-------allure测试报告生成完毕开始发送测试报告-------")
# 往allure测试报告中写入环境配置相关信息
env_info = ENV_VARS["common"]
env_info["run_env"] = GLOBAL_VARS.get("host", env)
AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_report_env_on_html(
env_info=env_info)
# 复制http_server.exe以及双击查看报告.bat文件到allure-html根目录下用于支撑电脑在未安装allure服务的情况下打开allure-html报告
# 注意ZIP文件的名称包含某些特殊字符会导致无法使用.bat文件打开allure-html报告 例如空格,/ 等
allure_config_path = os.path.join(CONF_DIR, "allure_config")
copy_file(src_file_path=os.path.join(allure_config_path,
[i for i in os.listdir(allure_config_path) if i.endswith(".exe")][0]),
dest_dir_path=ALLURE_HTML_DIR)
copy_file(src_file_path=os.path.join(allure_config_path,
[i for i in os.listdir(allure_config_path) if i.endswith(".bat")][0]),
dest_dir_path=ALLURE_HTML_DIR)
# report_path以及attachment_path后面发送测试结果需要用到
report_path = ALLURE_HTML_DIR
attachment_path = os.path.join(REPORT_DIR, f'autotest_{str(current_time)}.zip')
# 压缩allure-html报告为一个压缩文件zip
zip_file(in_path=ALLURE_HTML_DIR, out_path=attachment_path)
report_path, attachment_path = run_pytest(report_type=report, mark_param=m)
else:
# report_path以及attachment_path后面发送测试结果需要用到
report_path = os.path.join(REPORT_DIR, "autotest_" + str(current_time) + ".html")
attachment_path = report_path
pytest_html_config_path = os.path.join(CONF_DIR, "pytest_html_config")
report_css = os.path.join(pytest_html_config_path, "pytest_html_report.css")
arg_list.extend([f'--html={report_path}', f"--css={report_css}"])
pytest.main(args=arg_list)
logger.debug("-------测试完成,发送测试报告-------")
# ------------------------ 发送测试结果 ------------------------
# 发送通知
send_result(report_path, report_type=report, attachment_path=attachment_path)
except Exception as e:
raise e
if __name__ == '__main__':
if __name__ == "__main__":
run()