From 0fbd9eb5c8b42ad030103dfa400e92583aafac3d Mon Sep 17 00:00:00 2001 From: floraachy <1622042529@qq.com> Date: Wed, 2 Aug 2023 11:25:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=8A=A5=E5=91=8A=E9=98=89?= =?UTF-8?q?=E5=89=B2=E6=8E=89pytest-html=EF=BC=8C=E5=8F=AA=E6=94=AF?= =?UTF-8?q?=E6=8C=81allure=E7=94=9F=E6=88=90=E6=B5=8B=E8=AF=95=E6=8A=A5?= =?UTF-8?q?=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 2 - README.md | 20 +-- case_utils/get_results_handle.py | 79 +----------- case_utils/send_result_handle.py | 21 ++- common_utils/bs4_handle.py | 122 ------------------ config/case_template.txt | 17 ++- config/global_vars.py | 8 +- .../pytest_html_config/pytest_html_report.css | 54 -------- conftest.py | 93 +------------ pytest.ini | 1 - run.py | 119 ++++++++--------- test_case/test_manual_case/test_demo.py | 8 +- test_case/test_manual_case/test_login_demo.py | 23 ++-- 13 files changed, 102 insertions(+), 465 deletions(-) delete mode 100644 common_utils/bs4_handle.py delete mode 100644 config/pytest_html_config/pytest_html_report.css diff --git a/Pipfile b/Pipfile index 972c697..1acd44e 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,6 @@ name = "pip_conf_index_global" pymysql = "*" loguru = "*" requests-toolbelt = "*" -beautifulsoup4 = "*" requests = "*" openpyxl = "*" sshtunnel = "*" @@ -17,7 +16,6 @@ click = "*" faker = "*" jsonpath = "*" pytest = "==6.2.5" -pytest-html = "==2.1.1" pytest-rerunfailures = "*" allure-pytest = "==2.9.45" pydantic = "*" diff --git a/README.md b/README.md index c46c8ab..669ca54 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ 那么本自动化框架,将为你解决这些问题。 - 框架主要使用 python 语言编写,结合 pytest 进行二次开发,用户仅需要在 yaml 或者 excel 文件中编写测试用例, 编写成功之后,会自动生成测试用例代码,零基础代码小白,也可以操作。 - 如果是具备代码基础的,也可以直接通过 py 文件编写测试用例。 - - 使用 pytest-html / Allure 生成报告,并针对测试报告样式进行了调整,使得报告更加美观; + - 使用 Allure 生成报告,并针对测试报告样式进行了调整,使得报告更加美观; - 测试完成后,支持发送 企业微信通知/ 钉钉通知/ 邮箱通知,灵活配置。 ## 一、框架介绍 -本框架主要是基于 Python + pytest + pytest-html/Allure + loguru + 邮件通知/企业微信通知/钉钉通知 实现的接口自动化框架。 +本框架主要是基于 Python + pytest + Allure + loguru + 邮件通知/企业微信通知/钉钉通知 实现的接口自动化框架。 * git地址: [https://www.gitlink.org.cn/floraachy/apiautotest](https://www.gitlink.org.cn/floraachy/apiautotest) * 项目参与者: floraachy @@ -34,7 +34,7 @@ * 支持利用allure设置用例优先级,运行指定优先级的用例 * 测试数据隔离, 实现数据驱动 * 自动生成用例代码: 测试人员在yaml/excel文件中填写好测试用例, 程序可以直接生成用例代码,纯小白也能使用 -* 多种报告随心选择:框架支持pytest-html以及Allure测试报告,可以动态配置所需报告 +* 使用Allure生成测试报告,并对测试报告进行了定制化修改,使得测试报告更加美观 * 日志模块: 采用loguru管理日志,可以输出更为优雅,简洁的日志 * 钉钉、企业微信通知: 支持多种通知场景,执行成功之后,可选择发送钉钉、或者企业微信、邮箱通知 * 执行环境一键切换,解决多环境相互影响问题 @@ -52,14 +52,13 @@ │ ├────case_fun_handle.py 根据配置文件,从指定类型文件中读取用例数据,并调用生成用例文件方法,生成用例文件 │ ├────data_handle.py 数据处理 │ ├────extract_data_handle.py 提取数据的一些方法 -│ ├────get_results_handle.py 从pytest-html/allure测试报告中获取测试结果 +│ ├────get_results_handle.py 从allure测试报告中获取测试结果 │ ├────platform_handle.py 跨平台的支持allure,用于生成allure测试报告 │ ├────request_data_handle.py 针对用例数据进行请求前后的处理 -│ └────send_result_handle.py 根据配置文件,从html测试报告中获取测试结果,发送指定类型的通知 +│ └────send_result_handle.py 根据配置文件,发送指定类型的测试结果 ├────common_utils/ 公共的工具类 │ ├────__init__.py │ ├────base_request.py 封装的requests请求 -│ ├────bs4_handle.py bs4(BeautifulSoup4)是Python中的第三方库。可以从HTML或XML文件中提取数据的Python库。 │ ├────dingding_handle.py 封装的钉钉机器人 │ ├────excel_handle.py 处理excel │ ├────files_handle.py 处理文件相关操作 @@ -77,8 +76,6 @@ │ │ ├────http_server.exe http服务,用来放置在allure压缩包中,方便在不安装allure环境下打开allure报告 │ │ ├────logo.svg 保存用来替换allure报告的logo的,在代码中无用处 │ │ ├────双击打开Allure报告.bat .bat文件,用来放置在allure压缩包中,方便在不安装allure环境下打开allure报告 -│ ├────pytest_html_config/ -│ │ ├────pytest_html_report.css 改变pytest-html测试报告的样式文件 │ ├────case_template.txt 自动生成的测试用例文件模板 │ ├────global_vars.py 保存的一些全局变量 │ ├────path_config.py 项目路径管理 @@ -113,7 +110,6 @@ pymysql = "*" loguru = "*" requests-toolbelt = "*" -beautifulsoup4 = "*" requests = "*" openpyxl = "*" sshtunnel = "*" @@ -123,7 +119,6 @@ click = "*" faker = "*" jsonpath = "*" pytest = "==6.2.5" -pytest-html = "==2.1.1" pytest-rerunfailures = "*" allure-pytest = "==2.9.45" pydantic = "*" @@ -251,11 +246,8 @@ excel表单2名称是:示例模块 - 如果pycharm.interpreter拥有了框架所需的所有依赖包,可以通过pycharm直接在`run.py`中右键运行 ## 八、查看测试报告 -### pytest-html测试报告 -如果是pytest-html生成的测试报告,直接打开`outputs`目录下的`.html`报告即可。支持通过任意浏览器打开查看 - ### Allure测试报告 -1. 如果是Allure生成的测试报告,支持通过pycharm,点击`outputs/report/allure_html/index.html`打开查看测试报告 +1. Allure生成的测试报告,支持通过pycharm,点击`outputs/report/allure_html/index.html`打开查看测试报告 2. 如果不通过pycharm打开,直接通过文件夹打开,windows系统环境下,可以点击`outputs/report/allure_html/双击打开Allure报告.bat`打开查看测试报告 注意: diff --git a/case_utils/get_results_handle.py b/case_utils/get_results_handle.py index 28be18a..30896a9 100644 --- a/case_utils/get_results_handle.py +++ b/case_utils/get_results_handle.py @@ -5,86 +5,15 @@ # @Software: PyCharm # @Desc: 从测试报告中获取测试结果 +# 标准库导入 import os -from loguru import logger import json -from common_utils.bs4_handle import SoupAPI +# 第三方库导入 +from loguru import logger +# 本地应用/模块导入 from common_utils.time_handle import timestamp_strftime -def get_test_results_from_pytest_html_report(html_report_path): - """ - 通过BeautifulSoup4解析pytest-html生成的html报告,获取测试结果及测试情况 - :param html_report_path: pytest-html测试报告的绝对路径 - """ - try: - bs = SoupAPI(html_report_path) - test_results = {} - - # -------------- 获取测试环境信息 -------------- - environment_info = bs.get_element_by_id("environment").get_text() - new_environment_info = [element for element in environment_info.split("\n") if element != ''] - info_mapping = { - "Platform": "platform", - "Python": "python_version", - "开始时间": "start_time", - "项目名称": "project_name", - "项目环境": "run_env" - } - for key, value in enumerate(new_environment_info): - if value in info_mapping: - test_results[info_mapping[value]] = new_environment_info[key + 1] - - # -------------- 获取测试人员,所属部门,测试用例总数,运行时长 -------------- - p_elements = bs.get_elements_by_tag("p") - for p_element in p_elements: - res = p_element.get_text() - if "测试人员:" in res: - test_results['tester'] = res.replace("测试人员:", "") - if "所属部门: " in res: - test_results['department'] = res.replace("所属部门: ", "") - if "tests ran" in res: - new_list = res.split(" ") - test_results['run_time'] = f"{new_list[4]} 秒" - - # -------------- 获取具体结果 -------------- - result_mapping = { - 'passed': 'span.passed', # 通过的用例个数 - 'skipped': 'span.skipped', # 跳过的用例个数 - 'failed': 'span.failed', # 失败的用例个数 - 'broken': 'span.error', # 错误的用例个数 - 'xfailed': 'span.xfailed', # 预期失败的用例个数 - 'xpassed': 'span.xpassed', # 意外通过的用例个数 - 'rerun': 'span.rerun' # 重跑的用例个数 - } - for key, value in result_mapping.items(): - result_element = bs.select_element(value)[0] - test_results[key] = int(result_element.get_text().split(" ")[0]) - - # 用例总数 - test_results['total'] = test_results["passed"] + test_results["skipped"] + test_results["failed"] + \ - test_results["broken"] + test_results["xfailed"] + test_results["xpassed"] - # 通过率 - # 判断运行用例总数大于0 - if test_results['total'] > 0: - # 计算用例成功率 - test_results["pass_rate"] = round( - (test_results["passed"] + test_results["skipped"]) / test_results["total"] * 100, 2 - ) - else: - # 如果未运行用例,则成功率为 0.0 - test_results["pass_rate"] = 0.0 - - logger.debug(f"获取到的测试结果:{test_results}") - return test_results - except FileNotFoundError as e: - logger.error(f"程序中检查到您未生成pytest-html报告,通常可能导致的原因是pytest-html环境未配置正确,{e}") - raise FileNotFoundError( - "程序中检查到您未生成pytest-html报告," - "通常可能导致的原因是pytest-html环境未配置正确," - ) from e - - def get_test_results_from_from_allure_report(allure_html_path): """ 从allure生成的html报告的summary.json中,获取测试结果及测试情况 diff --git a/case_utils/send_result_handle.py b/case_utils/send_result_handle.py index 9425d55..773f208 100644 --- a/case_utils/send_result_handle.py +++ b/case_utils/send_result_handle.py @@ -3,15 +3,17 @@ # @Author : chenyinhua # @File : send_result_handle.py # @Software: PyCharm -# @Desc: 根据配置文件,发送指定通知 +# @Desc: 根据配置文件,发送指定类型的测试结果 +# 标准库导入 +# 第三方库导入 from loguru import logger +# 本地应用/模块导入 from config.models import NotificationType from config.settings import SEND_RESULT_TYPE, email, ding_talk, wechat, email_subject, email_content, ding_talk_title, \ ding_talk_content, wechat_content from case_utils.data_handle import data_handle -from case_utils.get_results_handle import get_test_results_from_pytest_html_report, \ - get_test_results_from_from_allure_report +from case_utils.get_results_handle import get_test_results_from_from_allure_report from common_utils.dingding_handle import DingTalkBot from common_utils.wechat_handle import WechatBot from common_utils.yagmail_handle import YagEmailServe @@ -70,11 +72,10 @@ def send_wechat(webhook_url, content, attachment=None): logger.error(f"发送企业微信通知异常, 错误信息:{e}") -def send_result(report_path, report_type="allure", attachment_path=None): +def send_result(report_path, attachment_path=None): """ 根据用户配置,采取指定方式,发送测试结果 :param report_path: 报告路径 - :param report_type: 报告类型,根据报告类型获取不同的测试结果, 类型可选值:allure, pytest-html :param attachment_path: 发送的附件, pytest-html就是报告本身作为附件发送, allure是压缩包发送 """ # 默认不发送任何通知 @@ -82,13 +83,9 @@ def send_result(report_path, report_type="allure", attachment_path=None): logger.info(f"SEND_RESULT_TYPE={SEND_RESULT_TYPE}, 配置了不发送任何邮件") print(f"SEND_RESULT_TYPE={SEND_RESULT_TYPE}, 配置了不发送任何邮件") return - if report_type.lower() == "allure": - results = get_test_results_from_from_allure_report(report_path) - elif report_type.lower() == "pytest_html": - results = get_test_results_from_pytest_html_report(report_path) - else: - logger.error(f"report_type={report_type}, 需要发送的报告类型错误,请检查一下配置信息,支持的类型为:allure, pytest-html") - return + + results = get_test_results_from_from_allure_report(report_path) + # 建立发送消息的内容、函数以及参数的映射关系 notification_mappings = { NotificationType.EMAIL.value: { diff --git a/common_utils/bs4_handle.py b/common_utils/bs4_handle.py deleted file mode 100644 index 30a5ab8..0000000 --- a/common_utils/bs4_handle.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2023/5/10 14:11 -# @Author : chenyinhua -# @File : bs4_handle.py -# @Software: PyCharm -# @Desc: bs4(BeautifulSoup4)是Python中的第三方库。可以从HTML或XML文件中提取数据的Python库。 - - -from bs4 import BeautifulSoup # pip install beautifulsoup4 - - -class SoupAPI: - def __init__(self, html_file): - self.html_file = html_file - self.soup = self.get_soup() - - def get_soup(self): - with open(self.html_file, 'r', encoding='utf-8') as f: - soup = BeautifulSoup(f.read(), 'html.parser') - return soup - - def get_element_text(self, element): - """ - 获取元素的文本内容 - ************************* - 在使用 BeautifulSoup 库解析 HTML 文件时,element.text 和 element.get_text() 都可以用来获取元素的文本内容。它们的区别在于: - element.text 返回的是元素及其子元素的文本内容,但是会将所有的换行符转换为空格。换言之,它可以将文本内容整合成一段连续的字符串,并去掉其中的换行符。 - element.get_text() 返回的也是元素及其子元素的文本内容,但是可以通过指定分隔符来保留换行符或者其他字符。 - 如果不指定分隔符,默认情况下会将所有的空白字符(包括换行符、制表符等)都替换成一个空格。 - 因此,如果你想保留换行符并且不需要在文本内容中保留其他空白字符,可以使用element.get_text('\n') 来指定换行符为分隔符。如果你只是想将文本内容整合为一行字符串,可以使用 element.text。 - ************************* - """ - return element.get_text() - - def get_element_by_id(self, element_id): - """ - 获取指定ID的元素 - """ - element = self.soup.find(id=element_id) - return element - - def get_elements_by_class(self, class_name): - """ - 获取指定Class的所有元素 - """ - elements = self.soup.find_all(class_=class_name) - return elements - - def get_elements_by_tag(self, tag_name): - """ - 获取指定标签的所有元素 - 例如:self.soup.find_all('p'), 获取所有的p标签 - """ - elements = self.soup.find_all(tag_name) - return elements - - def select_element(self, selector): - """ - 使用select筛选(select使用CSS选择规则) - soup.select(‘标签名'),代表根据标签来筛选出指定标签。 - soup.select(‘[属性名="属性值"]'),代表根据指定属性进行筛选; - CSS中#xxx代表筛选id,soup.select(‘#xxx')代表根据id筛选出指定标签,返回值是一个列表。 - CSS中.###代表筛选class,soup.select('.xxx')代表根据class筛选出指定标签,返回值是一个列表。 - 嵌套select: soup.select(“#xxx .xxxx”),如(“#id2 .news”)就是id=”id2”标签下class=”news的标签,返回值是一个列表。 - """ - return self.soup.select(selector) - - def get_elements_by_attr(self, attr_name, attr_value=True): - """ - 通过属性获取标签 - 如果是查找具体某个属性名称=属性值的标签,可以通过attr_name和attr_value来指定; - 如果仅想查找具备某个属性名称的标签,而不关心属性值是什么,就可以使用 True 来表示属性存在 - """ - return self.soup.find_all(attrs={f'{attr_name}': attr_value}) - - def get_element_parent(self, element): - """ - 查找指定元素的父元素; - """ - return element.parent - - def get_element_parents(self, element): - """ - 查找指定元素的所有祖先元素; - """ - return element.parents - - def get_element_next_sibling(self, element): - """ - 查找指定元素的下一个兄弟元素; - """ - return element.next_sibling - - def get_element_next_siblings(self, element): - """ - 查找指定元素的下一个所有的兄弟元素; - """ - return element.next_siblings - - def get_element_previous_sibling(self, element): - """ - 查找指定元素的上一个兄弟元素; - """ - return element.previous_sibling - - def get_element_previous_siblings(self, element): - """ - 查找指定元素的上一个所有兄弟元素; - """ - return element.previous_siblings - - def get_element_children(self, element): - """ - 查找指定元素的所有子元素; - """ - return element.children - - def get_element_descendants(self, element): - """ - 查找指定元素的所有子孙元素。 - """ - return element.descendants diff --git a/config/case_template.txt b/config/case_template.txt index 9e6a579..76c84db 100644 --- a/config/case_template.txt +++ b/config/case_template.txt @@ -1,15 +1,18 @@ # -*- coding: utf-8 -*- # @Time : ${now} +# 标准库导入 +# 第三方库导入 import pytest -from loguru import logger import allure +from loguru import logger +# 本地应用/模块导入 +from config.settings import db_info +from config.global_vars import GLOBAL_VARS from case_utils.assert_handle import assert_response, assert_sql from case_utils.request_data_handle import RequestPreDataHandle, RequestHandle, after_request_extract from case_utils.allure_handle import allure_title -from config.settings import db_info -from config.global_vars import GLOBAL_VARS -from pytest_html import extras # 往pytest-html报告中填写额外的内容 + # 用例数据 cases = ${case_data} @@ -22,19 +25,15 @@ class ${class_title}Auto: @allure.story("${allure_story}") @pytest.mark.auto @pytest.mark.parametrize("case", cases, ids=["{}".format(case["title"]) for case in cases]) - def ${func_title}_auto(self, case, extra): + def ${func_title}_auto(self, case): logger.info("\n-----------------------------START-开始执行用例-----------------------------\n") logger.debug(f"当前执行的用例数据:{case}") # 添加用例标题作为allure中显示的用例标题 allure_title(case.get("title", "")) # 处理请求前的用例数据 case_data = RequestPreDataHandle(case).request_data_handle() - # 将用例数据显示在pytest-html报告中 - extra.append(extras.text(str(case_data), name="用例数据")) # 发送请求 response = RequestHandle(case_data).http_request() - # 将响应数据显示在pytest-html报告中 - extra.append(extras.text(str(response.text), name="响应数据")) # 进行响应断言 assert_response(response, case_data["assert_response"]) # 进行数据库断言 diff --git a/config/global_vars.py b/config/global_vars.py index 507e827..78af19c 100644 --- a/config/global_vars.py +++ b/config/global_vars.py @@ -13,10 +13,10 @@ CUSTOM_MARKERS = [] ENV_VARS = { "common": { - "report_title": "自动化测试报告", - "project_name": "GitLink 确实开源", - "tester": "陈银花", - "department": "开源中心" + "报告标题": "自动化测试报告", + "项目名称": "GitLink 确实开源", + "测试人": "陈银花", + "所属部门": "开源中心" }, "test": { # 示例测试环境及示例测试账号 diff --git a/config/pytest_html_config/pytest_html_report.css b/config/pytest_html_config/pytest_html_report.css deleted file mode 100644 index 8967e9e..0000000 --- a/config/pytest_html_config/pytest_html_report.css +++ /dev/null @@ -1,54 +0,0 @@ -/* change pytest-html test report style */ -@charset "UTF-8"; -body { - font-size: 16px; -} - - -h1 { - text-align: center; - color: #2084D9; - font-size: 30px; -} - - -h2 { - font-size: 24px; - color: #2084D9; -} - - -a { - color: #466AFF; -} - -span { - font-size: 20px; -} - - -#environment td { - padding: 10px; -} - - -#results-table { - font-size: 16px; - table-layout:fixed; -} - -#results-table td{ - word-break:break-all; - word-wrap:break-word; -} - -#results-table th{ - font-size: 20px; - background-color: #2084D9; - color: #FFFFFF; -} - -td { - color: #000000; -} - diff --git a/conftest.py b/conftest.py index 68b64dc..77be440 100644 --- a/conftest.py +++ b/conftest.py @@ -7,62 +7,32 @@ # @Desc: 这是文件的描述信息 # 标准库导入 -import re import time -from time import strftime # 第三方库导入 from loguru import logger -from py._xmlgen import html # 安装pytest-html,版本最好是2.1.1 -import pytest # 本地应用/模块导入 -from config.global_vars import ENV_VARS, GLOBAL_VARS, CUSTOM_MARKERS +from config.global_vars import CUSTOM_MARKERS # ------------------------------------- START: pytest钩子函数处理---------------------------------------# def pytest_configure(config): """ - 1. 在测试运行前,修改Environment部分信息,配置测试报告环境信息 - 2. 注册自定义标记 + 注册自定义标记 """ - # 给环境表 添加项目名称及开始时间 - config._metadata["项目名称"] = ENV_VARS["common"]["project_name"] - config._metadata['开始时间'] = strftime('%Y-%m-%d %H:%M:%S') - # 给环境表 移除packages 及plugins - config._metadata.pop("Packages") - config._metadata.pop("Plugins") # 注册自定义标记 print(f"需要注册的标记:{CUSTOM_MARKERS}") + logger.debug(f"需要注册的标记:{CUSTOM_MARKERS}") markers = list(set(CUSTOM_MARKERS)) for custom_marker in markers: if isinstance(custom_marker, str): config.addinivalue_line('markers', f'{custom_marker}') print(f"注册了自定义标记:{custom_marker}") + logger.debug(f"注册了自定义标记:{custom_marker}") elif isinstance(custom_marker, dict): for k, v in custom_marker.items(): config.addinivalue_line('markers', f'{k}:{v}') print(f"注册了自定义标记:{custom_marker}") - - -@pytest.hookimpl(tryfirst=True) -def pytest_sessionfinish(session, exitstatus): - """ - 在测试运行后,修改Environment部分信息 - """ - # 给环境表 添加 项目环境 - session.config._metadata['项目环境'] = GLOBAL_VARS.get("host", "") - - -@pytest.mark.hookwrapper -def pytest_runtest_makereport(item, call): - """设置列"用例描述"的值为用例的标题title""" - outcome = yield - # 获取调用结果的测试报告,返回一个report对象 - # report对象的属性包括when(steup, 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] + logger.debug(f"注册了自定义标记:{custom_marker}") def pytest_terminal_summary(terminalreporter, config): @@ -109,57 +79,4 @@ def pytest_terminal_summary(terminalreporter, config): 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部分的信息 - """ - prefix.extend([html.p(f'测试人员:{ENV_VARS["common"]["tester"]}')]) - prefix.extend([html.p(f'所属部门: :{ENV_VARS["common"]["department"]}')]) - - -def pytest_html_results_table_header(cells): - """ - 修改结果表的表头 - """ - cells.pop(1) # 移除 "Test" 列 - # 往表格中增加一列"用例描述",并且给"用例描述"增加排序 - cells.insert(0, html.th('用例描述', class_="sortable", col="name")) - # 往表格中增加一列"用例方法",并且给"用例方法"增加排序 - cells.insert(1, html.th('用例方法', class_="sortable", col="name")) - # 往表格中增加一列"执行时间",并且给"执行时间"增加排序 - cells.insert(2, html.th('执行时间', class_="sortable time", col="time")) - - -@pytest.mark.optionalhook -def pytest_html_results_table_row(report, cells): - """ - 修改结果表的表头后给对应的行增加值 - """ - cells.pop(1) # 移除 "Test" 列 - # 往列"用例描述"插入每行的值 - cells.insert(0, html.td(report.description)) - # 往列"用例方法"插入每行的值 - cells.insert(1, html.td(report.func)) - # 往列"执行时间"插入每行的值 - cells.insert(2, html.td(strftime("%Y-%m-%d %H:%M:%S"), class_="col-time")) - - -def pytest_html_results_table_html(report, data): - """如果测试通过,则显示这条用例通过啦!""" - if report.passed: - del data[:] - data.append(html.div("这条用例通过啦!", class_="empty log")) - -# ------------------------------------- END: pytest-html钩子函数处理 ---------------------------------------# diff --git a/pytest.ini b/pytest.ini index c786e71..0834d90 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,7 +4,6 @@ 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 diff --git a/run.py b/run.py index b7f7c8a..2916e9f 100644 --- a/run.py +++ b/run.py @@ -13,7 +13,6 @@ > python run.py -m demo 在test环境仅运行打了标记demo用例, 默认报告采用allure > 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: 失败重跑次数 @@ -94,7 +93,7 @@ def auto_generate_test_cases(): # 封装执行 pytest 的函数 -def run_pytest(report_type, mark_param): +def run_pytest(mark_param): arg_list = [] # 执行指定的测试用例 @@ -104,79 +103,67 @@ def run_pytest(report_type, 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) + arg_list.extend( + [ + "-q", + "--cache-clear", + f'--alluredir={ALLURE_RESULTS_DIR}', + "--clean-alluredir", + ] + ) + pytest.main(args=arg_list) - # ------------------------ 使用allure生成测试报告 ------------------------ + # ------------------------ 使用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") + # 从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"]["项目名称"] + ) + # 设置allure报告名称 + AllureReportBeautiful(allure_html_path=ALLURE_HTML_DIR).set_report_name( + new_name=ENV_VARS["common"]["报告标题"] + ) + # 往allure测试报告中写入环境配置相关信息 + env_info = ENV_VARS["common"] + env_info["运行环境"] = 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) + 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) 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(report, env, m): +def run(env, m): try: # ------------------------ 捕获日志---------------------------- capture_all_logs() @@ -191,11 +178,11 @@ def run(report, env, m): auto_generate_test_cases() # ------------------------ pytest执行测试用例 ------------------------ - report_path, attachment_path = run_pytest(report_type=report, mark_param=m) + report_path, attachment_path = run_pytest(mark_param=m) # ------------------------ 发送测试结果 ------------------------ # 发送通知 - send_result(report_path, report_type=report, attachment_path=attachment_path) + send_result(report_path, attachment_path=attachment_path) except Exception as e: raise e diff --git a/test_case/test_manual_case/test_demo.py b/test_case/test_manual_case/test_demo.py index 1f687f5..89e76f9 100644 --- a/test_case/test_manual_case/test_demo.py +++ b/test_case/test_manual_case/test_demo.py @@ -6,11 +6,11 @@ # @Software: PyCharm # @Desc: python脚本编写的测试用例文件 - +# 第三方库导入 import pytest from loguru import logger -from pytest_html import extras # 往pytest-html报告中填写额外的内容 import allure +# 本地应用/模块导入 from case_utils.allure_handle import allure_title, custom_allure_step # 读取用例数据 @@ -21,13 +21,11 @@ cases = [{"title": "demo用例01", "severity": "blocker1", "user": "flora", "age @allure.story("demo模块(手动用例)") @pytest.mark.test_demo @pytest.mark.parametrize("case", cases, ids=["{}".format(case["title"]) for case in cases]) -def test_demo(case, extra): +def test_demo(case): logger.info("\n-----------------------------START-开始执行用例-----------------------------\n") logger.debug(f"当前执行的用例数据:{case}") # 添加用例标题作为allure中显示的用例标题 allure_title(case.get("title", "")) - # 将用例数据显示在pytest-html报告中 - extra.append(extras.json(case, name="用例数据")) # 在allure报告中显示请求的用例数据 custom_allure_step(step_title="用例数据", content=f"{case}") assert case["user"] == "flora" diff --git a/test_case/test_manual_case/test_login_demo.py b/test_case/test_manual_case/test_login_demo.py index c579645..a9b1557 100644 --- a/test_case/test_manual_case/test_login_demo.py +++ b/test_case/test_manual_case/test_login_demo.py @@ -6,19 +6,20 @@ # @Software: PyCharm # @Desc: python脚本编写的测试用例文件 - -import pytest +# 标准库导入 import os +# 第三方库导入 +import pytest +import allure +from loguru import logger +# 本地应用/模块导入 from common_utils.yaml_handle import YamlHandle from config.path_config import DATA_DIR -from case_utils.assert_handle import assert_response, assert_sql -from loguru import logger -from case_utils.request_data_handle import RequestPreDataHandle, RequestHandle, after_request_extract -from pytest_html import extras # 往pytest-html报告中填写额外的内容 -from case_utils.allure_handle import allure_title -import allure from config.settings import db_info from config.global_vars import GLOBAL_VARS +from case_utils.assert_handle import assert_response, assert_sql +from case_utils.request_data_handle import RequestPreDataHandle, RequestHandle, after_request_extract +from case_utils.allure_handle import allure_title # 读取用例数据 yaml_data = YamlHandle(filename=os.path.join(DATA_DIR, "gitlink", "login_demo.yaml")).read_yaml @@ -36,19 +37,15 @@ class TestLoginDemo: @allure.story(case_common["allure_story"]) @pytest.mark.test_demo @pytest.mark.parametrize("case", cases, ids=["{}".format(case["title"]) for case in cases]) - def test_login_demo_auto(self, case, extra): + def test_login_demo_auto(self, case): logger.info("\n-----------------------------START-开始执行用例-----------------------------\n") logger.debug(f"当前执行的用例数据:{case}") # 添加用例标题作为allure中显示的用例标题 allure_title(case.get("title", "")) # 处理请求前的用例数据 case_data = RequestPreDataHandle(case).request_data_handle() - # 将用例数据显示在pytest-html报告中 - extra.append(extras.text(str(case_data), name="用例数据")) # 发送请求 response = RequestHandle(case_data).http_request() - # 将响应数据显示在pytest-html报告中 - extra.append(extras.text(str(response.text), name="响应数据")) # 进行响应断言 assert_response(response, case_data["assert_response"]) # 进行数据库断言