测试报告阉割掉pytest-html,只支持allure生成测试报告
This commit is contained in:
2
Pipfile
2
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 = "*"
|
||||
|
||||
20
README.md
20
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`打开查看测试报告
|
||||
|
||||
注意:
|
||||
|
||||
@@ -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中,获取测试结果及测试情况
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
@@ -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"])
|
||||
# 进行数据库断言
|
||||
|
||||
@@ -13,10 +13,10 @@ CUSTOM_MARKERS = []
|
||||
|
||||
ENV_VARS = {
|
||||
"common": {
|
||||
"report_title": "自动化测试报告",
|
||||
"project_name": "GitLink 确实开源",
|
||||
"tester": "陈银花",
|
||||
"department": "开源中心"
|
||||
"报告标题": "自动化测试报告",
|
||||
"项目名称": "GitLink 确实开源",
|
||||
"测试人": "陈银花",
|
||||
"所属部门": "开源中心"
|
||||
},
|
||||
"test": {
|
||||
# 示例测试环境及示例测试账号
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
93
conftest.py
93
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钩子函数处理 ---------------------------------------#
|
||||
|
||||
@@ -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
|
||||
|
||||
119
run.py
119
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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"])
|
||||
# 进行数据库断言
|
||||
|
||||
Reference in New Issue
Block a user