1、优化断言 2、优化依赖接口调用
This commit is contained in:
@@ -149,7 +149,93 @@ case_info: 具体的用例数据,是以列表的形式进行管理
|
|||||||
assert_response:响应断言
|
assert_response:响应断言
|
||||||
assert_sql:数据库断言
|
assert_sql:数据库断言
|
||||||
```
|
```
|
||||||
### 6. Excel用例单独说明
|
|
||||||
|
### 6. 参数提取说明
|
||||||
|
目前支持3种方式的参数提取:type_jsonpath, type_re, type_response
|
||||||
|
#### type_jsonpath
|
||||||
|
如果采用jsonpath方式从响应数据提取参数,`extract`的key是`type_jsonpath`; `extract[type_jsonpath]`的key是变量名,value是提取表达式;
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
```
|
||||||
|
extract:
|
||||||
|
type_jsonpath:
|
||||||
|
nickname: $.username
|
||||||
|
login: $.login
|
||||||
|
user_id: $.user_id
|
||||||
|
```
|
||||||
|
注意:如果提取到的值是长度为1的列表,会自动获取第一个元素,其值类型由list变更为str。例如通过`name: $.data` 提取到的值是:`["flora"]`, 最后name的值会是`"flora"`。
|
||||||
|
|
||||||
|
|
||||||
|
#### type_re
|
||||||
|
如果采用正则表达式方式从响应数据提取参数,`extract`的key是`type_re`; `extract[type_re]`的key是变量名,value是提取表达式;
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
```
|
||||||
|
# 注意:\是用来转译的,保证yaml格式正确.也可以用引号包裹起来
|
||||||
|
extract:
|
||||||
|
type_re:
|
||||||
|
nickname: \"username":"(.*?)"
|
||||||
|
login: \"login":"(.*?)"
|
||||||
|
user_id: '"user_id":(.*?),'
|
||||||
|
```
|
||||||
|
注意:如果提取到的值是长度为1的列表,会自动获取第一个元素,其值类型由list变更为str。例如通过`name: "username":"(.*?)"` 提取到的值是:`["flora"]`, 最后name的值会是`"flora"`。
|
||||||
|
|
||||||
|
#### type_response
|
||||||
|
如果是直接从响应数据提取参数,`extract`的key是`type_response`; `extract[type_response]`的key是变量名,value是提取表达式;
|
||||||
|
|
||||||
|
基本上response所有方法都支持,部分参考:`response.status_code`, `response.cookies`, `response.text`, `response.headers`, `response.is_redirect`
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
```
|
||||||
|
extract:
|
||||||
|
type_response:
|
||||||
|
cookies: response.cookies
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 响应断言说明
|
||||||
|
#### 断言状态码
|
||||||
|
如果想要断言接口响应码,直接这样写即可:
|
||||||
|
参考示例:
|
||||||
|
```
|
||||||
|
assert_response:
|
||||||
|
status_code: 200
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应数据断言
|
||||||
|
响应断言的参数说明:
|
||||||
|
```
|
||||||
|
断言标识(自定义,不为空即可,没有实际的意义):
|
||||||
|
message: 断言信息,非必填,可为空
|
||||||
|
expect_value: 预期结果
|
||||||
|
assert_type: 断言类型,支持如下:==, lt, le, gt, ge, not_eq, str_eq, len_eq, len_gt, len_ge, len_lt, len_le, contains, contained_by, startswith, endswith
|
||||||
|
type_jsonpath: 通过jsonpath表达式通响应数据提取实际结果,与type_re任选其一
|
||||||
|
type_re: 通过正则表达式通响应数据提取实际结果,与type_jsonpath任选其一
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:在进行断言的时候,左侧是预期结果,右侧是实际结果。比如我断言类型是`lt`, 那么就是预期结果<实际结果
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
```
|
||||||
|
assert_response:
|
||||||
|
user_id:
|
||||||
|
message: 断言接口返回的user_id
|
||||||
|
expect_value: ${user_id}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.user_id
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
assert_response:
|
||||||
|
user_id:
|
||||||
|
expect_value: ${user_id}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.user_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 响应断言说明
|
||||||
|
todo 待补充ing
|
||||||
|
|
||||||
|
### 8. Excel用例单独说明
|
||||||
框架支持excel多表单自动生成测试用例,每一个表单作为一个测试用例模块。
|
框架支持excel多表单自动生成测试用例,每一个表单作为一个测试用例模块。
|
||||||
例如:
|
例如:
|
||||||
excel表格名称是:test_demo.xlsx
|
excel表格名称是:test_demo.xlsx
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ COMMON_DIR = os.path.join(BASE_DIR, "common_utils")
|
|||||||
CONF_DIR = os.path.join(BASE_DIR, "config")
|
CONF_DIR = os.path.join(BASE_DIR, "config")
|
||||||
|
|
||||||
# 测试数据模块目录
|
# 测试数据模块目录
|
||||||
DATA_DIR = os.path.join(BASE_DIR, "data")
|
DATA_DIR = os.path.join(BASE_DIR, "interface")
|
||||||
|
|
||||||
# 测试文件模块目录
|
# 测试文件模块目录
|
||||||
FILES_DIR = os.path.join(BASE_DIR, "files")
|
FILES_DIR = os.path.join(BASE_DIR, "files")
|
||||||
@@ -47,7 +47,7 @@ if not os.path.exists(MANUAL_CASE_DIR):
|
|||||||
os.mkdir(MANUAL_CASE_DIR)
|
os.mkdir(MANUAL_CASE_DIR)
|
||||||
|
|
||||||
# 测试用例方法模板路径
|
# 测试用例方法模板路径
|
||||||
CASE_TEMPLATE_DIR = os.path.join(CONF_DIR, "../utils/case_data_utils/case_template.txt")
|
CASE_TEMPLATE_DIR = os.path.join(CONF_DIR, "../utils/case_generate_utils/case_template.txt")
|
||||||
|
|
||||||
# 自动生成测试用例模块
|
# 自动生成测试用例模块
|
||||||
AUTO_CASE_DIR = os.path.join(CASE_DIR, "test_auto_case")
|
AUTO_CASE_DIR = os.path.join(CASE_DIR, "test_auto_case")
|
||||||
@@ -57,6 +57,7 @@ LIB_DIR = os.path.join(BASE_DIR, "lib")
|
|||||||
|
|
||||||
# Allure报告,测试结果集目录
|
# Allure报告,测试结果集目录
|
||||||
ALLURE_RESULTS_DIR = os.path.join(REPORT_DIR, "allure_results")
|
ALLURE_RESULTS_DIR = os.path.join(REPORT_DIR, "allure_results")
|
||||||
|
|
||||||
# Allure报告,HTML测试报告目录
|
# Allure报告,HTML测试报告目录
|
||||||
ALLURE_HTML_DIR = os.path.join(REPORT_DIR, "allure_html")
|
ALLURE_HTML_DIR = os.path.join(REPORT_DIR, "allure_html")
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -33,7 +33,7 @@ ENV_VARS = {
|
|||||||
"db_user": "root",
|
"db_user": "root",
|
||||||
"db_pwd": "**********",
|
"db_pwd": "**********",
|
||||||
"db_database": "test**********",
|
"db_database": "test**********",
|
||||||
"ssh": True,
|
"ssh": False,
|
||||||
"ssh_host": "xx.xx.xx.xx",
|
"ssh_host": "xx.xx.xx.xx",
|
||||||
"ssh_port": 3306,
|
"ssh_port": 3306,
|
||||||
"ssh_user": "root",
|
"ssh_user": "root",
|
||||||
@@ -90,7 +90,7 @@ class RunConfig:
|
|||||||
CASE_FILE_TYPE = 1
|
CASE_FILE_TYPE = 1
|
||||||
|
|
||||||
# 0表示默认不发送任何通知, 1 代表钉钉通知,2 代表企业微信通知, 3 代表邮件通知, 4 代表所有途径都发送通知
|
# 0表示默认不发送任何通知, 1 代表钉钉通知,2 代表企业微信通知, 3 代表邮件通知, 4 代表所有途径都发送通知
|
||||||
SEND_RESULT_TYPE = 3
|
SEND_RESULT_TYPE = 0
|
||||||
|
|
||||||
# 指定日志收集级别
|
# 指定日志收集级别
|
||||||
LOG_LEVEL = "DEBUG" # 可选值:TRACE DEBUG INFO SUCCESS WARNING ERROR CRITICAL
|
LOG_LEVEL = "DEBUG" # 可选值:TRACE DEBUG INFO SUCCESS WARNING ERROR CRITICAL
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
# 公共参数
|
|
||||||
case_common:
|
|
||||||
allure_epic: GitLink接口 # 敏捷里面的概念,定义史诗,相当于module级的标签, 往下是 feature
|
|
||||||
allure_feature: 登录模块 # 功能点的描述,相当于class级的标签, 理解成模块往下是 story
|
|
||||||
allure_story: 登录接口 # 故事,可以理解为场景,相当于method级的标签, 往下是 title
|
|
||||||
case_markers: # pytest框架的标记 pytest.mark.
|
|
||||||
- gitlink
|
|
||||||
- login: 登录接口
|
|
||||||
|
|
||||||
# 用例数据
|
|
||||||
case_info:
|
|
||||||
-
|
|
||||||
id: case_login_01
|
|
||||||
title: 用户名密码正确,登录成功(不校验数据库)
|
|
||||||
run: True
|
|
||||||
severity: normal
|
|
||||||
url: /api/accounts/login.json
|
|
||||||
method: POST
|
|
||||||
headers: {"Content-Type": "application/json; charset=utf-8;"}
|
|
||||||
cookies:
|
|
||||||
request_type: json
|
|
||||||
payload: { "login": "${login}","password": "${password}","autologin": 1 }
|
|
||||||
files:
|
|
||||||
extract:
|
|
||||||
type_re:
|
|
||||||
nickname: \"username":"(.*?)"
|
|
||||||
login: \"login":"(.*?)"
|
|
||||||
user_id: \"user_id":(.*?),
|
|
||||||
assert_response:
|
|
||||||
eq:
|
|
||||||
http_code: 200
|
|
||||||
$.user_id: ${user_id}
|
|
||||||
assert_sql:
|
|
||||||
|
|
||||||
-
|
|
||||||
id: case_login_02
|
|
||||||
title: 用户名密码正确,登录成功(校验数据库)
|
|
||||||
run: False
|
|
||||||
severity: minor
|
|
||||||
url: /api/accounts/login.json
|
|
||||||
method: POST
|
|
||||||
headers: {"Content-Type": "application/json; charset=utf-8;"}
|
|
||||||
cookies:
|
|
||||||
request_type: json
|
|
||||||
payload: { "login": "${login}","password": "${password}","autologin": 1 }
|
|
||||||
files:
|
|
||||||
extract:
|
|
||||||
nickname: $.username
|
|
||||||
login: $.login
|
|
||||||
user_id: $.user_id
|
|
||||||
assert_response:
|
|
||||||
eq:
|
|
||||||
http_code: 200
|
|
||||||
$.user_id: ${user_id}
|
|
||||||
assert_sql:
|
|
||||||
eq:
|
|
||||||
sql: select count(*) from tokens where user_id=${user_id};
|
|
||||||
len: 1
|
|
||||||
|
|
||||||
-
|
|
||||||
id: case_login_03
|
|
||||||
title: 用户名正确,密码错误,登录失败
|
|
||||||
severity: critical
|
|
||||||
run: False
|
|
||||||
url: /api/accounts/login.json
|
|
||||||
method: POST
|
|
||||||
headers: {"Content-Type": "application/json; charset=utf-8;"}
|
|
||||||
cookies:
|
|
||||||
request_type: json
|
|
||||||
payload: { "login": "chytest10","password": "password111","autologin": 1 }
|
|
||||||
files:
|
|
||||||
extract:
|
|
||||||
assert_response:
|
|
||||||
eq:
|
|
||||||
http_code: 200
|
|
||||||
$.status: -2
|
|
||||||
assert_sql:
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# @Version: Python 3.9
|
|
||||||
# @Time : 2023/1/9 16:41
|
|
||||||
# @Author : chenyinhua
|
|
||||||
# @File : demo_test_login.py
|
|
||||||
# @Software: PyCharm
|
|
||||||
# @Desc: python脚本编写的测试用例文件
|
|
||||||
|
|
||||||
# 第三方库导入
|
|
||||||
import pytest
|
|
||||||
from loguru import logger
|
|
||||||
import allure
|
|
||||||
# 本地应用/模块导入
|
|
||||||
from utils.report_utils.allure_handle import allure_title, allure_step
|
|
||||||
|
|
||||||
# 读取用例数据
|
|
||||||
cases = [{"title": "demo用例01", "severity": "blocker1", "user": "flora", "age": 17, "run": True},
|
|
||||||
{"title": "demo用例02", "severity": "TRIVIAL", "user": "flora", "age": 17, "run": True}]
|
|
||||||
|
|
||||||
|
|
||||||
@allure.story("demo模块(手动用例)")
|
|
||||||
@pytest.mark.test_demo
|
|
||||||
@pytest.mark.parametrize("case", cases, ids=["{}".format(case["title"]) for case in cases])
|
|
||||||
def test_demo(case):
|
|
||||||
logger.info("\n-----------------------------START-开始执行用例-----------------------------\n")
|
|
||||||
logger.debug(f"当前执行的用例数据:{case}")
|
|
||||||
# 添加用例标题作为allure中显示的用例标题
|
|
||||||
allure_title(case.get("title", ""))
|
|
||||||
# 在allure报告中显示请求的用例数据
|
|
||||||
allure_step(step_title="用例数据", content=f"{case}")
|
|
||||||
assert case["user"] == "flora"
|
|
||||||
logger.info("\n-----------------------------END-用例执行结束-----------------------------\n")
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# @Version: Python 3.9
|
|
||||||
# @Time : 2023/1/9 16:41
|
|
||||||
# @Author : chenyinhua
|
|
||||||
# @File : demo_test_login.py
|
|
||||||
# @Software: PyCharm
|
|
||||||
# @Desc: python脚本编写的测试用例文件
|
|
||||||
|
|
||||||
# 标准库导入
|
|
||||||
import os
|
|
||||||
# 第三方库导入
|
|
||||||
import pytest
|
|
||||||
import allure
|
|
||||||
from loguru import logger
|
|
||||||
# 本地应用/模块导入
|
|
||||||
from utils.files_utils.yaml_handle import YamlHandle
|
|
||||||
from config.path_config import DATA_DIR
|
|
||||||
from config.settings import db_info
|
|
||||||
from config.global_vars import GLOBAL_VARS
|
|
||||||
from utils.requests_utils.assert_handle import assert_response, assert_sql
|
|
||||||
from utils.requests_utils.request_control import RequestPreDataHandle, RequestHandle, after_request_extract
|
|
||||||
from utils.report_utils.allure_handle import allure_title
|
|
||||||
|
|
||||||
# 读取用例数据
|
|
||||||
yaml_data = YamlHandle(filename=os.path.join(DATA_DIR, "gitlink", "test_login.yaml")).read_yaml
|
|
||||||
case_common = yaml_data["case_common"]
|
|
||||||
cases = yaml_data["case_info"]
|
|
||||||
|
|
||||||
|
|
||||||
@allure.epic(case_common["allure_epic"])
|
|
||||||
@allure.feature(case_common["allure_feature"])
|
|
||||||
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):
|
|
||||||
logger.info("\n-----------------------------START-开始执行用例-----------------------------\n")
|
|
||||||
logger.debug(f"当前执行的用例数据:{case}")
|
|
||||||
# 添加用例标题作为allure中显示的用例标题
|
|
||||||
allure_title(case.get("title", ""))
|
|
||||||
# 处理请求前的用例数据
|
|
||||||
case_data = RequestPreDataHandle(case).request_data_handle()
|
|
||||||
# 发送请求
|
|
||||||
response = RequestHandle(case_data).http_request()
|
|
||||||
# 进行响应断言
|
|
||||||
assert_response(response, case_data["assert_response"])
|
|
||||||
# 进行数据库断言
|
|
||||||
assert_sql(db_info[GLOBAL_VARS["env_key"]], case_data["assert_sql"])
|
|
||||||
# 断言成功后进行参数提取
|
|
||||||
after_request_extract(response, case_data.get("extract", None))
|
|
||||||
logger.info("\n-----------------------------END-用例执行结束-----------------------------\n")
|
|
||||||
+2
-6
@@ -25,9 +25,7 @@ case_info:
|
|||||||
files:
|
files:
|
||||||
extract:
|
extract:
|
||||||
assert_response:
|
assert_response:
|
||||||
eq:
|
status_code: 200
|
||||||
http_code: 200
|
|
||||||
$.message: success
|
|
||||||
assert_sql:
|
assert_sql:
|
||||||
|
|
||||||
-
|
-
|
||||||
@@ -47,7 +45,5 @@ case_info:
|
|||||||
files:
|
files:
|
||||||
extract:
|
extract:
|
||||||
assert_response:
|
assert_response:
|
||||||
eq:
|
status_code: 200
|
||||||
http_code: 200
|
|
||||||
$.message: success
|
|
||||||
assert_sql:
|
assert_sql:
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
case_common:
|
||||||
|
allure_epic: GitLink接口
|
||||||
|
allure_feature: 开源项目模块
|
||||||
|
allure_story: 项目
|
||||||
|
case_markers:
|
||||||
|
- gitlink
|
||||||
|
- usefixtures: gitlink_login
|
||||||
|
|
||||||
|
# 用例数据
|
||||||
|
case_info:
|
||||||
|
-
|
||||||
|
id: gitlink_projects_get_ignores_01
|
||||||
|
title: 获取gitignore列表数据
|
||||||
|
severity: normal
|
||||||
|
run: True
|
||||||
|
url: /api/ignores.json
|
||||||
|
method: GET
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json; charset=utf-8;
|
||||||
|
cookies: ${cookies}
|
||||||
|
cookies:
|
||||||
|
request_type: params
|
||||||
|
payload:
|
||||||
|
files:
|
||||||
|
extract:
|
||||||
|
assert_response:
|
||||||
|
status_code: 200
|
||||||
|
assert_sql:
|
||||||
+56
-27
@@ -2,24 +2,24 @@
|
|||||||
case_common:
|
case_common:
|
||||||
allure_epic: GitLink接口 # 敏捷里面的概念,定义史诗,相当于module级的标签, 往下是 feature
|
allure_epic: GitLink接口 # 敏捷里面的概念,定义史诗,相当于module级的标签, 往下是 feature
|
||||||
allure_feature: 开源项目模块 # 功能点的描述,相当于class级的标签, 理解成模块往下是 story
|
allure_feature: 开源项目模块 # 功能点的描述,相当于class级的标签, 理解成模块往下是 story
|
||||||
allure_story: 新建项目接口 # 故事,可以理解为场景,相当于method级的标签, 往下是 title
|
allure_story: 项目 # 故事,可以理解为场景,相当于method级的标签, 往下是 title
|
||||||
case_markers:
|
case_markers:
|
||||||
- gitlink
|
- gitlink
|
||||||
- new_project
|
- new_project
|
||||||
- usefixtures: login_init
|
- usefixtures: gitlink_login
|
||||||
|
|
||||||
# 用例数据
|
# 用例数据
|
||||||
case_info:
|
case_info:
|
||||||
-
|
-
|
||||||
id: case_new_project_01
|
id: gitlink_projects_new_project_01
|
||||||
title: 正确输入各项必填参数,新建项目成功(不校验数据库, header里面传cookies)
|
title: 正确输入各项必填参数,新建公开项目成功
|
||||||
severity: critical
|
severity: critical
|
||||||
run: True
|
run: True
|
||||||
url: /api/projects.json
|
url: /api/projects.json
|
||||||
method: POST
|
method: POST
|
||||||
headers:
|
headers:
|
||||||
Content-Type: application/json; charset=utf-8;
|
Content-Type: application/json; charset=utf-8;
|
||||||
cookies: ${login_cookie}
|
cookies: ${cookies}
|
||||||
cookies:
|
cookies:
|
||||||
request_type: json
|
request_type: json
|
||||||
payload:
|
payload:
|
||||||
@@ -28,51 +28,76 @@ case_info:
|
|||||||
repository_name: ${generate_identifier()}
|
repository_name: ${generate_identifier()}
|
||||||
files:
|
files:
|
||||||
extract:
|
extract:
|
||||||
type_json:
|
type_jsonpath:
|
||||||
project_id: $.id
|
project_id: $.id
|
||||||
project_name: $.name
|
project_name: $.name
|
||||||
project_identifier: $.identifier
|
project_identifier: $.identifier
|
||||||
assert_response:
|
assert_response:
|
||||||
eq:
|
status_code: 200
|
||||||
http_code: 200
|
login:
|
||||||
$.login: ${login}
|
message: 断言接口返回的login
|
||||||
|
expect_value: ${login}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.login
|
||||||
assert_sql:
|
assert_sql:
|
||||||
|
|
||||||
-
|
-
|
||||||
id: case_new_project_02
|
id: gitlink_projects_new_project_02
|
||||||
title: 正确输入各项必填参数,新建项目成功(不校验数据库,单独传cookies)
|
title: 正确输入所有参数,新建私有项目成功
|
||||||
run: True
|
run: false
|
||||||
url: /api/projects.json
|
url: /api/projects.json
|
||||||
method: POST
|
method: POST
|
||||||
headers:
|
headers:
|
||||||
Content-Type: application/json; charset=utf-8;
|
Content-Type: application/json; charset=utf-8;
|
||||||
cookies: ${login_cookie}
|
cookies: ${cookies}
|
||||||
request_type: json
|
request_type: json
|
||||||
payload:
|
payload:
|
||||||
user_id: ${user_id}
|
user_id: ${user_id}
|
||||||
name: ${generate_name(lan='zh')}_${generate_identifier()}
|
name: ${generate_name(lan='zh')}_${generate_identifier()}
|
||||||
repository_name: ${generate_identifier()}
|
repository_name: ${generate_identifier()}
|
||||||
|
description:
|
||||||
|
private: true
|
||||||
|
ignoreFlag: true
|
||||||
|
ignore_id:
|
||||||
|
ignore:
|
||||||
|
licenseFlag: true
|
||||||
|
license_id:
|
||||||
|
license:
|
||||||
|
categoreFlag: true
|
||||||
|
project_category_id:
|
||||||
|
project_category:
|
||||||
|
languageFlag: true
|
||||||
|
project_language_id:
|
||||||
|
project_language:
|
||||||
|
blockchain: false
|
||||||
|
blockchain_token_all: 10000
|
||||||
|
blockchain_init_token:
|
||||||
|
auth_password:
|
||||||
files:
|
files:
|
||||||
extract:
|
extract:
|
||||||
type_json:
|
type_jsonpath:
|
||||||
project_id: $.id
|
project_id: $.id
|
||||||
project_name: $.name
|
project_name: $.name
|
||||||
project_identifier: $.identifier
|
project_identifier: $.identifier
|
||||||
assert_response:
|
assert_response:
|
||||||
eq:
|
status_code: 200
|
||||||
http_code: 200
|
login:
|
||||||
$.login: ${login}
|
message: 断言接口返回的login
|
||||||
|
expect_value: ${login}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.login
|
||||||
assert_sql:
|
assert_sql:
|
||||||
|
|
||||||
-
|
-
|
||||||
id: case_new_project_03
|
id: gitlink_projects_new_project_03
|
||||||
title: 正确输入各项必填参数,新建项目成功(校验数据库)
|
title: 正确输入各项必填参数,新建项目成功(校验数据库)
|
||||||
severity: normal
|
severity: normal
|
||||||
run: False
|
run: false
|
||||||
url: /api/projects.json
|
url: /api/projects.json
|
||||||
method: POST
|
method: POST
|
||||||
headers: {"Content-Type": "application/json; charset=utf-8;"}
|
headers:
|
||||||
cookies:
|
Content-Type: application/json; charset=utf-8;
|
||||||
|
cookies: ${cookies}
|
||||||
request_type: json
|
request_type: json
|
||||||
payload:
|
payload:
|
||||||
user_id: ${user_id}
|
user_id: ${user_id}
|
||||||
@@ -80,13 +105,17 @@ case_info:
|
|||||||
repository_name: ${generate_identifier()}
|
repository_name: ${generate_identifier()}
|
||||||
files:
|
files:
|
||||||
extract:
|
extract:
|
||||||
project_id: $.id
|
type_re:
|
||||||
project_name: $.name
|
project_id: $.id
|
||||||
project_identifier: $.identifier
|
project_name: $.name
|
||||||
|
project_identifier: $.identifier
|
||||||
assert_response:
|
assert_response:
|
||||||
eq:
|
status_code: 200
|
||||||
http_code: 200
|
login:
|
||||||
$.login: ${login}
|
message: 断言接口返回的login
|
||||||
|
expect_value: ${login}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.login
|
||||||
assert_sql:
|
assert_sql:
|
||||||
eq:
|
eq:
|
||||||
sql: select id,`name`, identifier from projects where user_id=${user_id} ORDER BY created_on DESC;
|
sql: select id,`name`, identifier from projects where user_id=${user_id} ORDER BY created_on DESC;
|
||||||
+3
-5
@@ -6,7 +6,7 @@ case_common:
|
|||||||
case_markers:
|
case_markers:
|
||||||
- gitlink
|
- gitlink
|
||||||
- upload_file
|
- upload_file
|
||||||
- usefixtures: login_init
|
- usefixtures: gitlink_login
|
||||||
|
|
||||||
# 用例数据
|
# 用例数据
|
||||||
case_info:
|
case_info:
|
||||||
@@ -24,10 +24,8 @@ case_info:
|
|||||||
payload:
|
payload:
|
||||||
files: TOC出库订单导入模板(2).xlsx
|
files: TOC出库订单导入模板(2).xlsx
|
||||||
extract:
|
extract:
|
||||||
type_json:
|
type_jsonpath:
|
||||||
file_id: $.id
|
file_id: $.id
|
||||||
assert_response:
|
assert_response:
|
||||||
eq:
|
status_code: 200
|
||||||
http_code: 200
|
|
||||||
|
|
||||||
assert_sql:
|
assert_sql:
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# 公共参数
|
||||||
|
case_common:
|
||||||
|
allure_epic: GitLink接口 # 敏捷里面的概念,定义史诗,相当于module级的标签, 往下是 feature
|
||||||
|
allure_feature: 登录模块 # 功能点的描述,相当于class级的标签, 理解成模块往下是 story
|
||||||
|
allure_story: 登录接口 # 故事,可以理解为场景,相当于method级的标签, 往下是 title
|
||||||
|
case_markers: # pytest框架的标记 pytest.mark.
|
||||||
|
- gitlink
|
||||||
|
- login: 登录接口
|
||||||
|
|
||||||
|
# 用例数据
|
||||||
|
case_info:
|
||||||
|
-
|
||||||
|
id: gitlink_login_01
|
||||||
|
title: 用户名密码正确,登录成功(不校验数据库)
|
||||||
|
run: True
|
||||||
|
severity: normal
|
||||||
|
url: /api/accounts/login.json
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json; charset=utf-8;
|
||||||
|
cookies:
|
||||||
|
request_type: json
|
||||||
|
payload:
|
||||||
|
login: ${login}
|
||||||
|
password: ${password}
|
||||||
|
autologin: 1
|
||||||
|
files:
|
||||||
|
extract:
|
||||||
|
type_re:
|
||||||
|
nickname: \"username":"(.*?)"
|
||||||
|
login: \"login":"(.*?)"
|
||||||
|
user_id: \"user_id":(.*?),
|
||||||
|
type_response:
|
||||||
|
cookies: response.cookies
|
||||||
|
assert_response:
|
||||||
|
status_code: 200
|
||||||
|
user_id:
|
||||||
|
message: 断言接口返回的user_id
|
||||||
|
expect_value: ${user_id}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.user_id
|
||||||
|
login:
|
||||||
|
message: 断言接口返回的login
|
||||||
|
expect_value: ${login}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.login
|
||||||
|
assert_sql:
|
||||||
|
|
||||||
|
-
|
||||||
|
id: case_login_02
|
||||||
|
title: 用户名密码正确,登录成功(校验数据库)
|
||||||
|
run: false
|
||||||
|
severity: minor
|
||||||
|
url: /api/accounts/login.json
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json; charset=utf-8;
|
||||||
|
cookies:
|
||||||
|
request_type: json
|
||||||
|
payload:
|
||||||
|
login: ${login}
|
||||||
|
password: ${password}
|
||||||
|
autologin: 1
|
||||||
|
files:
|
||||||
|
extract:
|
||||||
|
type_jsonpath:
|
||||||
|
nickname: $.username
|
||||||
|
login: $.login
|
||||||
|
user_id: $.user_id
|
||||||
|
assert_response:
|
||||||
|
status_code: 200
|
||||||
|
user_id:
|
||||||
|
expect_value: ${user_id}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.user_id
|
||||||
|
login:
|
||||||
|
message: 断言接口返回的login
|
||||||
|
expect_value: ${login}
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.login
|
||||||
|
assert_sql:
|
||||||
|
contains_user:
|
||||||
|
message: 断言数据库:查询tokens表,表中存在该登录用户记录;预期查询结果的长度为1
|
||||||
|
sql: select * from tokens where user_id=${user_id};
|
||||||
|
expect_value: 1
|
||||||
|
assert_type: len_eq
|
||||||
|
contains_user2:
|
||||||
|
message: 断言数据库:查询tokens表,表中存在该登录用户记录;预期 ${user_id} in 查询结果(jsonpath式匹配后的结果)
|
||||||
|
sql: select * from tokens where user_id=${user_id};
|
||||||
|
type_jsonpath: $..user_id
|
||||||
|
expect_value: ${user_id}
|
||||||
|
assert_type: ==
|
||||||
|
contains_user3:
|
||||||
|
message: 断言数据库:查询tokens表,表中存在该登录用户记录;预期 ${user_id} in 查询结果(正则表达式匹配后的结果)
|
||||||
|
sql: select * from tokens;
|
||||||
|
type_re: "'user_id': (.*?),"
|
||||||
|
expect_value: ${user_id}
|
||||||
|
assert_type: contains
|
||||||
|
|
||||||
|
-
|
||||||
|
id: case_login_03
|
||||||
|
title: 用户名正确,密码错误,登录失败
|
||||||
|
severity: critical
|
||||||
|
run: true
|
||||||
|
url: /api/accounts/login.json
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json; charset=utf-8;
|
||||||
|
cookies:
|
||||||
|
request_type: json
|
||||||
|
payload:
|
||||||
|
login: ${login}
|
||||||
|
password: 12345678900
|
||||||
|
autologin: 1
|
||||||
|
files:
|
||||||
|
extract:
|
||||||
|
assert_response:
|
||||||
|
status_code: 200
|
||||||
|
user_id:
|
||||||
|
message: 断言接口返回的status
|
||||||
|
expect_value: -2
|
||||||
|
assert_type: ==
|
||||||
|
type_jsonpath: $.status
|
||||||
|
assert_sql:
|
||||||
@@ -40,7 +40,7 @@ import pytest
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
import click
|
import click
|
||||||
# 本地应用/模块导入
|
# 本地应用/模块导入
|
||||||
from utils.case_data_utils.case_fun_generate import generate_cases
|
from utils.case_generate_utils.case_fun_generate import generate_cases
|
||||||
from utils.report_utils.send_result_handle import send_result
|
from utils.report_utils.send_result_handle import send_result
|
||||||
from config.path_config import REPORT_DIR, LOG_DIR, CONF_DIR, ALLURE_RESULTS_DIR, ALLURE_HTML_DIR, AUTO_CASE_DIR
|
from config.path_config import REPORT_DIR, LOG_DIR, CONF_DIR, ALLURE_RESULTS_DIR, ALLURE_HTML_DIR, AUTO_CASE_DIR
|
||||||
from config.settings import LOG_LEVEL, RunConfig, ENV_VARS
|
from config.settings import LOG_LEVEL, RunConfig, ENV_VARS
|
||||||
|
|||||||
+7
-26
@@ -4,6 +4,7 @@
|
|||||||
# @File : conftest.py
|
# @File : conftest.py
|
||||||
# @Software: PyCharm
|
# @Software: PyCharm
|
||||||
# @Desc:
|
# @Desc:
|
||||||
|
import os.path
|
||||||
|
|
||||||
# 标准库导入
|
# 标准库导入
|
||||||
# 第三方库导入
|
# 第三方库导入
|
||||||
@@ -12,8 +13,9 @@ import allure
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
# 本地应用/模块导入
|
# 本地应用/模块导入
|
||||||
from config.global_vars import GLOBAL_VARS
|
from config.global_vars import GLOBAL_VARS
|
||||||
from utils.requests_utils.base_request import BaseRequest
|
from config.path_config import DATA_DIR
|
||||||
from utils.report_utils.allure_handle import allure_title
|
from utils.report_utils.allure_handle import allure_title
|
||||||
|
from utils.requests_utils.api_workflow import get_api_data, api_work_flow
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function", autouse=True)
|
@pytest.fixture(scope="function", autouse=True)
|
||||||
@@ -70,33 +72,12 @@ def pytest_collection_modifyitems(config, items):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def login_init():
|
def gitlink_login():
|
||||||
"""
|
"""
|
||||||
获取登录的cookie
|
获取登录的cookie
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
host = GLOBAL_VARS.get("host")
|
|
||||||
login = GLOBAL_VARS.get('login')
|
|
||||||
password = GLOBAL_VARS.get('password')
|
|
||||||
# 兼容一下host后面多一个斜线的情况
|
|
||||||
if host[-1] == "/":
|
|
||||||
host = host[:len(host) - 1]
|
|
||||||
req_data = {
|
|
||||||
"url": host + "/api/accounts/login.json",
|
|
||||||
"method": "POST",
|
|
||||||
"request_type": "json",
|
|
||||||
"headers": {"Content-Type": "application/json; charset=utf-8;"},
|
|
||||||
"payload": {"login": login, "password": password, "autologin": 1}
|
|
||||||
}
|
|
||||||
# 请求登录接口
|
# 请求登录接口
|
||||||
try:
|
login_api = get_api_data(os.path.join(DATA_DIR, "gitlink", "test_login.yaml"), "gitlink_login_01")
|
||||||
res = BaseRequest.send_request(req_data=req_data)
|
api_work_flow(login_api, GLOBAL_VARS)
|
||||||
res.raise_for_status()
|
|
||||||
logger.debug(f"获取用户:{login}登录的cookies成功:{type(res.cookies)} || {res.cookies}")
|
|
||||||
GLOBAL_VARS["login_cookie"] = res.cookies
|
|
||||||
GLOBAL_VARS["login"] = res.json()["login"]
|
|
||||||
GLOBAL_VARS["nickname"] = res.json()["username"]
|
|
||||||
GLOBAL_VARS["user_id"] = res.json()["user_id"]
|
|
||||||
except Exception as e:
|
|
||||||
GLOBAL_VARS["login_cookie"] = None
|
|
||||||
logger.error(f"获取用户:{login}登录的cookies失败:{e}")
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2023/12/12 10:59
|
||||||
|
# @Author : floraachy
|
||||||
|
# @File : __init__.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc:
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/12/12 14:31
|
||||||
|
# @Author : floraachy
|
||||||
|
# @File : assert_control.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 断言类型封装,支持json响应断言、正则表达式响应断言、数据库断言
|
||||||
|
|
||||||
|
# 标准库导入
|
||||||
|
import types
|
||||||
|
# 第三方库导入
|
||||||
|
import allure
|
||||||
|
from requests import Response
|
||||||
|
from loguru import logger
|
||||||
|
# 本地应用/模块导入
|
||||||
|
from utils.models import AssertMethod
|
||||||
|
from utils.assertion_utils import assert_function
|
||||||
|
from utils.data_utils.extract_data_handle import json_extractor, re_extract
|
||||||
|
from utils.database_utils.mysql_handle import MysqlServer
|
||||||
|
|
||||||
|
|
||||||
|
class AssertUtils:
|
||||||
|
|
||||||
|
def __init__(self, assert_data, response: Response = None, db_info: dict = None):
|
||||||
|
"""
|
||||||
|
断言处理
|
||||||
|
:param assert_data: 断言数据
|
||||||
|
:param response: 接口响应数据
|
||||||
|
:param db_info: 数据库信息
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.assert_data = assert_data
|
||||||
|
self.response = response
|
||||||
|
if db_info:
|
||||||
|
self.db_connect = MysqlServer(**db_info)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_message(self):
|
||||||
|
"""
|
||||||
|
获取断言描述,如果未填写,则返回 `None`
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.assert_data.get("message", "")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_assert_type(self):
|
||||||
|
"""
|
||||||
|
检查assert_type是否是模型类AssertMethod中指定的值
|
||||||
|
"""
|
||||||
|
assert 'assert_type' in self.assert_data.keys(), (
|
||||||
|
" 断言数据: '%s' 中缺少 `assert_type` 属性 " % self.assert_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取断言类型对应的枚举值
|
||||||
|
name = AssertMethod(self.assert_data.get("assert_type")).name
|
||||||
|
return name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_sql_result(self):
|
||||||
|
"""
|
||||||
|
通过数据库查询获取查询结果
|
||||||
|
"""
|
||||||
|
if "sql" not in self.assert_data.keys() or self.assert_data["sql"] is None:
|
||||||
|
logger.error(f"断言数据: {self.assert_data} 缺少 'sql' 属性或 'sql' 为空")
|
||||||
|
raise ValueError("断言数据: {self.assert_data} 缺少 'sql' 属性或 'sql' 为空")
|
||||||
|
return self.db_connect.query_all(sql=self.assert_data["sql"])
|
||||||
|
|
||||||
|
def get_actual_value_by_response(self):
|
||||||
|
"""
|
||||||
|
通过jsonpath表达式从响应数据中获取实际结果
|
||||||
|
通过jsonpath表达式从响应数据中获取实际结果
|
||||||
|
"""
|
||||||
|
if "type_jsonpath" in self.assert_data and self.assert_data["type_jsonpath"]:
|
||||||
|
return json_extractor(obj=self.response.json(), expr=self.assert_data["type_jsonpath"])
|
||||||
|
if "type_re" in self.assert_data and self.assert_data["type_re"]:
|
||||||
|
return re_extract(obj=self.response.text, expr=self.assert_data["type_re"])
|
||||||
|
else:
|
||||||
|
assert 'type_re' or 'type_jsonpath' in self.assert_data.keys(), (
|
||||||
|
" 断言数据: '%s' 中缺少 `type_re` 或 `type_jsonpath` 属性 " % self.assert_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_actual_value_by_sql(self):
|
||||||
|
"""
|
||||||
|
通过jsonpath表达式从数据库查询结果中获取实际结果
|
||||||
|
通过正则表达式从数据库查询结果中获取实际结果
|
||||||
|
"""
|
||||||
|
if "type_jsonpath" in self.assert_data and self.assert_data["type_jsonpath"]:
|
||||||
|
return json_extractor(obj=self.get_sql_result, expr=self.assert_data["type_jsonpath"])
|
||||||
|
elif "type_re" in self.assert_data and self.assert_data["type_re"]:
|
||||||
|
return re_extract(obj=str(self.get_sql_result), expr=self.assert_data["type_re"])
|
||||||
|
else:
|
||||||
|
return self.get_sql_result
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_expect_value(self):
|
||||||
|
"""
|
||||||
|
获取预期结果, 断言数据中应该存在key=expect_value
|
||||||
|
"""
|
||||||
|
assert 'expect_value' in self.assert_data.keys(), (
|
||||||
|
f"断言数据: {self.assert_data} 中缺少 `value` 属性 "
|
||||||
|
)
|
||||||
|
return self.assert_data.get("expect_value")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def assert_function_mapping(self):
|
||||||
|
"""
|
||||||
|
断言方法匹配, 获取utils\assertion_utils\assert_function.py中的方法并返回
|
||||||
|
"""
|
||||||
|
# 从 module中获取方法的名称和所在的内存地址 """
|
||||||
|
module_functions = {}
|
||||||
|
|
||||||
|
for name, item in vars(assert_function).items():
|
||||||
|
if isinstance(item, types.FunctionType):
|
||||||
|
module_functions[name] = item
|
||||||
|
return module_functions
|
||||||
|
|
||||||
|
def assert_handle(self):
|
||||||
|
"""
|
||||||
|
断言处理
|
||||||
|
"""
|
||||||
|
if "sql" in self.assert_data.keys():
|
||||||
|
actual_value = self.get_actual_value_by_sql()
|
||||||
|
|
||||||
|
else:
|
||||||
|
actual_value = self.get_actual_value_by_response()
|
||||||
|
|
||||||
|
expect_value = self.get_expect_value
|
||||||
|
message = str(self.get_message)
|
||||||
|
assert_type = self.get_assert_type
|
||||||
|
logger.debug(f"\nmessage: {message}\n"
|
||||||
|
f"assert_type: {assert_type}\n"
|
||||||
|
f"expect_value: {expect_value}\n"
|
||||||
|
f"actual_value: {actual_value}\n")
|
||||||
|
message = message or f"断言 --> 预期结果:{type(expect_value)} || {expect_value} 实际结果:{type(actual_value)} || {actual_value}"
|
||||||
|
with allure.step(message):
|
||||||
|
# 调用utils.assertion_utils.assert_type里面的方法
|
||||||
|
self.assert_function_mapping[assert_type](expect_value=expect_value, actual_value=actual_value,
|
||||||
|
message=message)
|
||||||
|
|
||||||
|
|
||||||
|
class AssertHandle(AssertUtils):
|
||||||
|
def get_assert_data_list(self):
|
||||||
|
"""
|
||||||
|
获取所有的断言数据,并以列表的形式返回
|
||||||
|
"""
|
||||||
|
assert_list = []
|
||||||
|
if self.assert_data and isinstance(self.assert_data, dict):
|
||||||
|
for k, v in self.assert_data.items():
|
||||||
|
if k.lower() == "status_code":
|
||||||
|
with allure.step("断言 --> 响应状态码"):
|
||||||
|
assert_function.equals(expect_value=v, actual_value=self.response.status_code)
|
||||||
|
else:
|
||||||
|
assert_list.append(v)
|
||||||
|
else:
|
||||||
|
logger.debug(f"断言数据为空或者不是字典格式,跳过断言!\n"
|
||||||
|
f"断言数据:{self.assert_data}")
|
||||||
|
return assert_list
|
||||||
|
|
||||||
|
def assert_handle(self):
|
||||||
|
"""
|
||||||
|
将收集到的断言数据逐一进行断言
|
||||||
|
"""
|
||||||
|
for value in self.get_assert_data_list():
|
||||||
|
self.assert_data = value
|
||||||
|
super().assert_handle()
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/12/12 14:31
|
||||||
|
# @Author : floraachy
|
||||||
|
# @File : assert_function.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 断言方法
|
||||||
|
|
||||||
|
|
||||||
|
# 标准库导入
|
||||||
|
from typing import Any, Union, Text
|
||||||
|
# 第三方库导入
|
||||||
|
import allure
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} == 实际结果:{actual_value}")
|
||||||
|
def equals(expect_value: Any, actual_value: Any, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断是否相等
|
||||||
|
"""
|
||||||
|
assert expect_value == actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} < 实际结果:{actual_value}")
|
||||||
|
def less_than(expect_value: Union[int, float], actual_value: Union[int, float], message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断预期结果小于实际结果
|
||||||
|
"""
|
||||||
|
assert expect_value < actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} <= 实际结果:{actual_value}")
|
||||||
|
def less_than_or_equals(expect_value: Union[int, float], actual_value: Union[int, float], message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断预期结果小于等于实际结果
|
||||||
|
"""
|
||||||
|
assert expect_value <= actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} > 实际结果:{actual_value}")
|
||||||
|
def greater_than(expect_value: Union[int, float], actual_value: Union[int, float], message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断预期结果大于实际结果
|
||||||
|
"""
|
||||||
|
assert expect_value > actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} >= 实际结果:{actual_value}")
|
||||||
|
def greater_than_or_equals(expect_value: Union[int, float], actual_value: Union[int, float], message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断预期结果大于等于实际结果
|
||||||
|
"""
|
||||||
|
assert expect_value >= actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} != 实际结果:{actual_value}")
|
||||||
|
def not_equals(expect_value: Any, actual_value: Any, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断预期结果不等于实际结果
|
||||||
|
"""
|
||||||
|
assert expect_value != actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} == 实际结果:{actual_value}")
|
||||||
|
def string_equals(expect_value: Any, actual_value: Text, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断字符串是否相等
|
||||||
|
"""
|
||||||
|
assert expect_value == actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("长度相等 --> 预期结果:{expect_value} == 实际结果:{actual_value}")
|
||||||
|
def length_equals(expect_value: int, actual_value: Text, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断长度是否相等
|
||||||
|
"""
|
||||||
|
assert isinstance(
|
||||||
|
expect_value, int
|
||||||
|
), "expect_value 需要为 int 类型"
|
||||||
|
assert expect_value == len(actual_value), message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("长度大于 --> 预期结果:{expect_value} > 实际结果:{actual_value}")
|
||||||
|
def length_greater_than(expect_value: Union[int, float], actual_value: Text, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断长度大于
|
||||||
|
"""
|
||||||
|
assert isinstance(
|
||||||
|
expect_value, (float, int)
|
||||||
|
), "expect_value 需要为 float/int 类型"
|
||||||
|
assert len(str(actual_value)) > expect_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("长度大于等于 --> 预期结果:{expect_value} >= 实际结果:{actual_value}")
|
||||||
|
def length_greater_than_or_equals(expect_value: Union[int, float], actual_value: Text, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断长度大于等于
|
||||||
|
"""
|
||||||
|
assert isinstance(
|
||||||
|
expect_value, (int, float)
|
||||||
|
), "expect_value 需要为 float/int 类型"
|
||||||
|
assert expect_value >= len(actual_value), message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("长度小于 --> 预期结果:{expect_value} < 实际结果:{actual_value}")
|
||||||
|
def length_less_than(expect_value: Union[int, float], actual_value: Text, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断长度小于
|
||||||
|
"""
|
||||||
|
assert isinstance(
|
||||||
|
expect_value, (int, float)
|
||||||
|
), "expect_value 需要为 float/int 类型"
|
||||||
|
assert expect_value < len(actual_value), message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("长度小于等于 --> 预期结果:{expect_value} <= 实际结果:{actual_value}")
|
||||||
|
def length_less_than_or_equals(expect_value: Union[int, float], actual_value: Text, message: Text = ""):
|
||||||
|
"""判断长度小于等于"""
|
||||||
|
assert isinstance(
|
||||||
|
expect_value, (int, float)
|
||||||
|
), "expect_value 需要为 float/int 类型"
|
||||||
|
assert expect_value <= len(actual_value), message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("预期结果:{expect_value} in 实际结果:{actual_value}")
|
||||||
|
def contains(expect_value: Any, actual_value: Any, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断预期结果内容包含在实际结果中
|
||||||
|
"""
|
||||||
|
assert isinstance(
|
||||||
|
actual_value, (list, tuple, dict, str, bytes)
|
||||||
|
), "actual_value 需要为 list/tuple/dict/str/bytes 类型"
|
||||||
|
assert expect_value in actual_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("实际结果:{expect_value} in 预期结果:{actual_value}")
|
||||||
|
def contained_by(expect_value: Any, actual_value: Any, message: Text = ""):
|
||||||
|
"""
|
||||||
|
判断实际结果包含在预期结果中
|
||||||
|
"""
|
||||||
|
assert isinstance(
|
||||||
|
actual_value, (list, tuple, dict, str, bytes)
|
||||||
|
), "actual_value 需要为 list/tuple/dict/str/bytes 类型"
|
||||||
|
|
||||||
|
assert actual_value in expect_value, message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("实际结果:{actual_value} 是以 预期结果: {expect_value} 开头的")
|
||||||
|
def startswith(expect_value: Any, actual_value: Any, message: Text = ""):
|
||||||
|
"""
|
||||||
|
检查实际结果的开头是否和预期结果内容的开头相等
|
||||||
|
"""
|
||||||
|
assert str(actual_value).startswith(str(expect_value)), message
|
||||||
|
|
||||||
|
|
||||||
|
@allure.step("实际结果:{actual_value} 是以 预期结果: {expect_value} 结尾的")
|
||||||
|
def endswith(expect_value: Any, actual_value: Any, message: Text = ""):
|
||||||
|
"""
|
||||||
|
检查实际结果的结尾是否和预期结果内容相等
|
||||||
|
"""
|
||||||
|
assert str(actual_value).endswith(str(expect_value)), message
|
||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
# 标准库导入
|
# 标准库导入
|
||||||
from typing import Text
|
from typing import Text
|
||||||
# 第三方库导入
|
# 第三方库导入
|
||||||
from utils.case_data_utils.models import TestCaseEnum, Method, RequestType, Severity
|
from utils.models import TestCaseEnum, Method, RequestType, Severity
|
||||||
|
|
||||||
|
|
||||||
class CaseDataCheck:
|
class CaseDataCheck:
|
||||||
+2
-2
@@ -16,11 +16,11 @@ from loguru import logger
|
|||||||
from utils.files_utils.excel_handle import ExcelHandle
|
from utils.files_utils.excel_handle import ExcelHandle
|
||||||
from utils.files_utils.yaml_handle import YamlHandle
|
from utils.files_utils.yaml_handle import YamlHandle
|
||||||
from utils.files_utils.files_handle import get_files, get_relative_path
|
from utils.files_utils.files_handle import get_files, get_relative_path
|
||||||
from utils.case_data_utils.models import CaseFileType
|
from utils.models import CaseFileType
|
||||||
from config.settings import CASE_FILE_TYPE
|
from config.settings import CASE_FILE_TYPE
|
||||||
from config.path_config import DATA_DIR, CASE_TEMPLATE_DIR, AUTO_CASE_DIR
|
from config.path_config import DATA_DIR, CASE_TEMPLATE_DIR, AUTO_CASE_DIR
|
||||||
from config.global_vars import CUSTOM_MARKERS
|
from config.global_vars import CUSTOM_MARKERS
|
||||||
from utils.case_data_utils.case_data_analysis import CaseDataCheck
|
from utils.case_generate_utils.case_data_analysis import CaseDataCheck
|
||||||
|
|
||||||
"""
|
"""
|
||||||
主要步骤:
|
主要步骤:
|
||||||
+4
-6
@@ -5,12 +5,10 @@
|
|||||||
# 第三方库导入
|
# 第三方库导入
|
||||||
import pytest
|
import pytest
|
||||||
import allure
|
import allure
|
||||||
from loguru import logger
|
|
||||||
# 本地应用/模块导入
|
# 本地应用/模块导入
|
||||||
from config.global_vars import GLOBAL_VARS
|
from config.global_vars import GLOBAL_VARS
|
||||||
from utils.requests_utils.assert_handle import assert_response, assert_sql
|
|
||||||
from utils.requests_utils.request_control import RequestPreDataHandle, RequestHandle, after_request_extract
|
from utils.requests_utils.request_control import RequestPreDataHandle, RequestHandle, after_request_extract
|
||||||
from utils.report_utils.allure_handle import allure_title
|
from utils.assertion_utils.assert_control import AssertHandle
|
||||||
|
|
||||||
|
|
||||||
# 用例数据
|
# 用例数据
|
||||||
@@ -30,12 +28,12 @@ class ${class_title}Auto:
|
|||||||
# 发送请求
|
# 发送请求
|
||||||
response = RequestHandle(case_data=case_data, global_var=GLOBAL_VARS).http_request()
|
response = RequestHandle(case_data=case_data, global_var=GLOBAL_VARS).http_request()
|
||||||
# 进行响应断言
|
# 进行响应断言
|
||||||
assert_response(response, case_data["assert_response"])
|
AssertHandle(assert_data=case_data["assert_response"], response=response).assert_handle()
|
||||||
# 进行数据库断言
|
# 进行数据库断言
|
||||||
assert_sql(db_info=GLOBAL_VARS["db_info"], expected=case_data["assert_sql"])
|
AssertHandle(assert_data=case_data["assert_sql"], db_info=GLOBAL_VARS["db_info"]).assert_handle()
|
||||||
# 断言成功后进行参数提取
|
# 断言成功后进行参数提取
|
||||||
res = after_request_extract(response, case_data.get("extract", None))
|
res = after_request_extract(response, case_data.get("extract", None))
|
||||||
for k,v in res.items():
|
for k, v in res.items():
|
||||||
GLOBAL_VARS[k] = v
|
GLOBAL_VARS[k] = v
|
||||||
|
|
||||||
|
|
||||||
@@ -11,9 +11,11 @@ import re
|
|||||||
from jsonpath import jsonpath
|
from jsonpath import jsonpath
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from requests import Response
|
from requests import Response
|
||||||
|
# 本地应用/模块导入
|
||||||
|
from utils.data_utils.data_handle import data_handle
|
||||||
|
|
||||||
|
|
||||||
def json_extractor(obj: dict, expr: str = '.'):
|
def json_extractor(obj, expr: str = '.'):
|
||||||
"""
|
"""
|
||||||
从目标对象obj, 根据表达式expr提取指定的值
|
从目标对象obj, 根据表达式expr提取指定的值
|
||||||
:param obj :json/dict类型数据
|
:param obj :json/dict类型数据
|
||||||
@@ -22,12 +24,14 @@ def json_extractor(obj: dict, expr: str = '.'):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result = jsonpath(obj, expr)[0] if len(jsonpath(obj, expr)) == 1 else jsonpath(obj, expr)
|
result = jsonpath(obj, expr)[0] if len(jsonpath(obj, expr)) == 1 else jsonpath(obj, expr)
|
||||||
logger.trace(f"\n提取表达式: {expr} \n"
|
logger.debug(f"\n提取对象:{obj}\n"
|
||||||
|
f"提取表达式: {expr} \n"
|
||||||
f"提取值类型: {type(result)}\n"
|
f"提取值类型: {type(result)}\n"
|
||||||
f"提取值:{result}\n")
|
f"提取值:{result}\n")
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.trace(f"\n提取表达式: {expr}\n"
|
logger.debug(f"\n提取对象:{obj}\n"
|
||||||
|
f"提取表达式: {expr}\n"
|
||||||
f"提取对象: {obj}\n"
|
f"提取对象: {obj}\n"
|
||||||
f"错误信息:{e}\n")
|
f"错误信息:{e}\n")
|
||||||
|
|
||||||
@@ -42,12 +46,16 @@ def re_extract(obj: str, expr: str = '.'):
|
|||||||
try:
|
try:
|
||||||
# 如果提取后的数据长度为1,则取第一个元素(返回str),否则返回列表
|
# 如果提取后的数据长度为1,则取第一个元素(返回str),否则返回列表
|
||||||
result = re.findall(expr, obj)[0] if len(re.findall(expr, obj)) == 1 else re.findall(expr, obj)
|
result = re.findall(expr, obj)[0] if len(re.findall(expr, obj)) == 1 else re.findall(expr, obj)
|
||||||
logger.debug(f"\n提取表达式: {expr}\n"
|
# 由于提取出来的数据都是str格式,将eval一样,还原数据格式
|
||||||
|
result = data_handle(obj=result)
|
||||||
|
logger.debug(f"\n提取对象:{obj}\n"
|
||||||
|
f"提取表达式: {expr}\n"
|
||||||
f"提取值类型: {type(result)}\n"
|
f"提取值类型: {type(result)}\n"
|
||||||
f"提取值:{result}\n")
|
f"提取值:{result}\n")
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"\n提取表达式: {expr}\n"
|
logger.debug(f"\n提取对象:{obj}\n"
|
||||||
|
f"提取表达式: {expr}\n"
|
||||||
f"提取对象: {obj}\n"
|
f"提取对象: {obj}\n"
|
||||||
f"错误信息:{e}\n")
|
f"错误信息:{e}\n")
|
||||||
|
|
||||||
@@ -69,3 +77,11 @@ def response_extract(response: Response, expr: str = '.'):
|
|||||||
logger.debug(f"\n提取表达式: {expr}\n"
|
logger.debug(f"\n提取表达式: {expr}\n"
|
||||||
f"提取对象: {response}\n"
|
f"提取对象: {response}\n"
|
||||||
f"错误信息:{e}\n")
|
f"错误信息:{e}\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
obj = [{'id': 1, 'user_id': 102, 'action': 'autologin', 'value': '3734462a398eedd9ab7448c4e2880ddd3f9bb2cb'}]
|
||||||
|
expre = "'user_id': (.*?),"
|
||||||
|
|
||||||
|
res = re_extract(obj=str(obj), expr=expre)
|
||||||
|
print(res)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class MysqlServer:
|
|||||||
f"SQL: {sql}\n" \
|
f"SQL: {sql}\n" \
|
||||||
f"result: {data}\n" \
|
f"result: {data}\n" \
|
||||||
"=====================================================")
|
"=====================================================")
|
||||||
return self.verify(data)
|
return data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{sql} --> 报错: {e}")
|
logger.error(f"{sql} --> 报错: {e}")
|
||||||
raise e
|
raise e
|
||||||
@@ -112,7 +112,7 @@ class MysqlServer:
|
|||||||
f"SQL: {sql}\n" \
|
f"SQL: {sql}\n" \
|
||||||
f"result: {data}\n" \
|
f"result: {data}\n" \
|
||||||
"=====================================================")
|
"=====================================================")
|
||||||
return self.verify(data)
|
return data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{sql} --> 报错: {e}")
|
logger.error(f"{sql} --> 报错: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
@@ -135,3 +135,27 @@ class RequestType(Enum):
|
|||||||
FILE = 'FILE'
|
FILE = 'FILE'
|
||||||
EXPORT = "EXPORT"
|
EXPORT = "EXPORT"
|
||||||
NONE = "NONE"
|
NONE = "NONE"
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class AssertMethod(Enum):
|
||||||
|
"""
|
||||||
|
断言类型
|
||||||
|
注意:这里的类型与assert_type.py中的方法名相同,不要随意改动
|
||||||
|
"""
|
||||||
|
equals = "=="
|
||||||
|
less_than = "lt"
|
||||||
|
less_than_or_equals = "le"
|
||||||
|
greater_than = "gt"
|
||||||
|
greater_than_or_equals = "ge"
|
||||||
|
not_equals = "not_eq"
|
||||||
|
string_equals = "str_eq"
|
||||||
|
length_equals = "len_eq"
|
||||||
|
length_greater_than = "len_gt"
|
||||||
|
length_greater_than_or_equals = 'len_ge'
|
||||||
|
length_less_than = "len_lt"
|
||||||
|
length_less_than_or_equals = 'len_le'
|
||||||
|
contains = "contains"
|
||||||
|
contained_by = 'contained_by'
|
||||||
|
startswith = 'startswith'
|
||||||
|
endswith = 'endswith'
|
||||||
@@ -11,7 +11,7 @@ import json
|
|||||||
# 第三方库导入
|
# 第三方库导入
|
||||||
import allure
|
import allure
|
||||||
# 本地应用/模块导入
|
# 本地应用/模块导入
|
||||||
from utils.case_data_utils.models import AllureAttachmentType
|
from utils.models import AllureAttachmentType
|
||||||
from utils.report_utils.platform_handle import PlatformHandle
|
from utils.report_utils.platform_handle import PlatformHandle
|
||||||
from utils.files_utils.files_handle import zip_file, copy_file
|
from utils.files_utils.files_handle import zip_file, copy_file
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# 第三方模块
|
# 第三方模块
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
# 本地应用/模块导入
|
# 本地应用/模块导入
|
||||||
from utils.case_data_utils.models import NotificationType
|
from utils.models import NotificationType
|
||||||
from config.settings import SEND_RESULT_TYPE, email, ding_talk, wechat, email_subject, email_content, ding_talk_title, \
|
from config.settings import SEND_RESULT_TYPE, email, ding_talk, wechat, email_subject, email_content, ding_talk_title, \
|
||||||
ding_talk_content, wechat_content
|
ding_talk_content, wechat_content
|
||||||
from utils.data_utils.data_handle import data_handle
|
from utils.data_utils.data_handle import data_handle
|
||||||
|
|||||||
@@ -7,43 +7,75 @@
|
|||||||
|
|
||||||
|
|
||||||
# 标准库导入
|
# 标准库导入
|
||||||
|
import os
|
||||||
# 第三方库导入
|
# 第三方库导入
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
# 本地应用/模块导入
|
# 本地应用/模块导入
|
||||||
from utils.files_utils.yaml_handle import YamlHandle
|
from utils.files_utils.yaml_handle import YamlHandle
|
||||||
from utils.requests_utils.request_control import RequestPreDataHandle, RequestHandle, after_request_extract
|
from utils.requests_utils.request_control import RequestPreDataHandle, RequestHandle, after_request_extract
|
||||||
from utils.requests_utils.assert_handle import assert_response
|
from utils.files_utils.files_handle import get_files
|
||||||
|
from utils.data_utils.eval_data_handle import eval_data
|
||||||
|
from utils.assertion_utils.assert_control import AssertHandle
|
||||||
|
from config.global_vars import GLOBAL_VARS
|
||||||
|
|
||||||
|
|
||||||
class ApiWorkFlow:
|
def get_api_data(api_file_path: str, key: str = None):
|
||||||
def __init__(self, api_file_path: str):
|
"""
|
||||||
"""
|
根据指定的yaml文件路径,以及key值,获取对应的接口
|
||||||
:param:api_file_path 接口yaml文件路径
|
:param:api_file_path 接口yaml文件路径
|
||||||
:param:key 对应接口的id
|
:param:key 对应接口的id
|
||||||
:param:global_var 全局变量,保存接口相关变量的实际值, 例如接口中的${login},会从GLOBAL_VAR中找到key=login的元素进行替换
|
"""
|
||||||
"""
|
api_data = []
|
||||||
self.api_file_path = api_file_path
|
if os.path.isdir(api_file_path):
|
||||||
|
logger.debug(f"目标路径是一个目录:{api_file_path}")
|
||||||
|
api_files = get_files(target=api_file_path, end=".yaml")
|
||||||
|
for api_file in api_files:
|
||||||
|
api_data.append(YamlHandle(filename=api_file).read_yaml)
|
||||||
|
|
||||||
def get_api_mode(self, key):
|
elif os.path.isfile(api_file_path):
|
||||||
"""
|
logger.debug(f"目标路径是一个文件:{api_file_path}")
|
||||||
根据指定的yaml文件路径,以及key值,获取对应的接口
|
api_data.append(YamlHandle(filename=api_file_path).read_yaml)
|
||||||
:param:key 对应接口的id
|
|
||||||
"""
|
else:
|
||||||
api_modes = YamlHandle(filename=self.api_file_path).read_yaml
|
logger.error(f"目标路径错误,请检查!api_file_path={api_file_path}")
|
||||||
matching_api = next((item for item in api_modes["case_info"] if item["id"] == key), None)
|
return None
|
||||||
|
|
||||||
|
for api in api_data:
|
||||||
|
matching_api = next((item for item in api["case_info"] if item["id"] == key), None)
|
||||||
if matching_api:
|
if matching_api:
|
||||||
|
logger.info("\n----------匹配到的api----------\n"
|
||||||
|
f"类型:{type(matching_api)}"
|
||||||
|
f"值:{matching_api}\n")
|
||||||
return matching_api
|
return matching_api
|
||||||
else:
|
|
||||||
logger.error(f"根据{key}, 未能找到对应的接口")
|
|
||||||
raise KeyError(f"根据{key}, 未能找到对应的接口")
|
|
||||||
|
|
||||||
def api_work_flow(self, key: str, global_var: dict = {}) -> dict:
|
# 在找不到匹配的情况下,返回一个默认值且记录一条错误日志
|
||||||
api_mode = self.get_api_mode(key)
|
logger.warning(f"未找到id为{key}的接口, 返回值是None")
|
||||||
api_info = RequestPreDataHandle(request_data=api_mode, global_var=global_var).request_data_handle()
|
return None # 或者根据需求返回其他默认值
|
||||||
|
|
||||||
|
|
||||||
|
def api_work_flow(req_data: dict, source: dict) -> dict:
|
||||||
|
"""
|
||||||
|
请求过程:请求前用例数据处理,发送请求,断言,参数提取
|
||||||
|
:param:req_data 接口请求数据
|
||||||
|
:param:source 全局变量,保存接口相关变量的实际值, 例如接口中的${login},会从source中找到key=login的元素进行替换
|
||||||
|
"""
|
||||||
|
logger.debug(f"\n----------------api_work_flow-----------------\n"
|
||||||
|
f"接口请求数据:{req_data}\n"
|
||||||
|
f"全局变量:{source}\n")
|
||||||
|
if req_data:
|
||||||
|
extract_result = {}
|
||||||
|
api_data = RequestPreDataHandle(request_data=req_data, global_var=source).request_data_handle()
|
||||||
# 发送请求
|
# 发送请求
|
||||||
response = RequestHandle(api_info, global_var).http_request()
|
response = RequestHandle(case_data=api_data, global_var=source).http_request()
|
||||||
# 进行响应断言
|
# 进行响应断言
|
||||||
assert_response(response, api_info["assert_response"])
|
AssertHandle(assert_data=api_data["assert_response"], response=response).assert_handle()
|
||||||
# 断言成功后进行参数提取
|
# 断言成功后进行参数提取
|
||||||
res = after_request_extract(response, api_info["extract"])
|
res = after_request_extract(response, api_data["extract"])
|
||||||
return res
|
for k, v in res.items():
|
||||||
|
extract_result[k] = eval_data(v)
|
||||||
|
GLOBAL_VARS.update(extract_result)
|
||||||
|
return extract_result
|
||||||
|
else:
|
||||||
|
logger.error(f"接口请求数据不能为空!\n"
|
||||||
|
f"req_data = {req_data}")
|
||||||
|
raise f"接口请求数据不能为空!\nreq_data = {req_data}"
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# @Version: Python 3.9
|
|
||||||
# @Time : 2023/2/1 14:31
|
|
||||||
# @Author : chenyinhua
|
|
||||||
# @File : assert_handle.py
|
|
||||||
# @Software: PyCharm
|
|
||||||
# @Desc: 断言
|
|
||||||
|
|
||||||
# 第三方库导入
|
|
||||||
from loguru import logger
|
|
||||||
from requests import Response
|
|
||||||
import allure
|
|
||||||
# 本地应用/模块导入
|
|
||||||
from utils.data_utils.extract_data_handle import json_extractor, re_extract
|
|
||||||
from utils.report_utils.allure_handle import allure_step
|
|
||||||
from utils.database_utils.mysql_handle import MysqlServer
|
|
||||||
|
|
||||||
|
|
||||||
@allure.step("响应断言 --> 响应数据:{response} - 预期结果:{expected}")
|
|
||||||
def assert_response(response: Response, expected: dict) -> None:
|
|
||||||
""" 断言方法
|
|
||||||
:param response: 实际响应对象
|
|
||||||
:param expected: 预期响应内容,从excel中或者yaml读取、或者手动传入,格式如下:
|
|
||||||
{
|
|
||||||
'eq':
|
|
||||||
{'http_code': 200, '$.user_id': '85392'},
|
|
||||||
"in":
|
|
||||||
{'message': "success"}
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
"""
|
|
||||||
logger.info("\n======================================================\n" \
|
|
||||||
f"-------------Start:响应断言--------------------\n"
|
|
||||||
f"响应断言预期结果:{expected}, {type(expected)}")
|
|
||||||
if expected is None:
|
|
||||||
return
|
|
||||||
index = 0
|
|
||||||
for k, v in expected.items():
|
|
||||||
# 获取需要断言的实际结果部分
|
|
||||||
for _k, _v in v.items():
|
|
||||||
if _k == "http_code":
|
|
||||||
actual = response.status_code
|
|
||||||
else:
|
|
||||||
if response_type(response) == "json":
|
|
||||||
# 如果响应数据是json格式
|
|
||||||
actual = json_extractor(response.json(), _k)
|
|
||||||
else:
|
|
||||||
# 响应数据不是json格式
|
|
||||||
actual = re_extract(response.text, _k)
|
|
||||||
index += 1
|
|
||||||
logger.info(
|
|
||||||
f'第{index}个响应断言 -|- 预期结果: {_k}: {_v}, {type(_v)} {k} 实际结果: {actual}, {type(actual)}')
|
|
||||||
allure_step(
|
|
||||||
step_title=f'第{index}个响应断言数据---->预期结果: {_k}: {_v}, {type(_v)} {k} 实际结果: {actual}, {type(actual)}')
|
|
||||||
try:
|
|
||||||
if k == "eq": # 预期结果 = 实际结果
|
|
||||||
assert _v == actual
|
|
||||||
logger.success(f"预期结果: {_k}: {_v} == 实际结果: {actual}, 断言成功!")
|
|
||||||
elif k == "in": # 实际结果 包含 预期结果
|
|
||||||
assert _v in actual
|
|
||||||
logger.success(f"预期结果: {_k}: {_v} in 实际结果: {actual}, 断言成功!")
|
|
||||||
elif k == "gt": # 预期结果 > 实际结果 (值应该为数值型)
|
|
||||||
assert _v > actual
|
|
||||||
logger.success(f"预期结果: {_k}: {_v} > 实际结果: {actual}, 断言成功!")
|
|
||||||
elif k == "lt": # 预期结果 < 实际结果 (值应该为数值型)
|
|
||||||
assert _v < actual
|
|
||||||
logger.success(f"预期结果: {_k}: {_v} < 实际结果: {actual}, 断言成功!")
|
|
||||||
elif k == "not": # 预期结果 != 实际结果
|
|
||||||
assert _v != actual
|
|
||||||
logger.success(f"预期结果: {_k}: {_v} != 实际结果: {actual}, 断言成功!")
|
|
||||||
else:
|
|
||||||
logger.error(f"判断关键字: {k} 错误!, 目前仅支持如下关键字:eq, in, gt, lt, not")
|
|
||||||
allure_step(step_title=f'判断关键字: {k} 错误!',
|
|
||||||
content='目前仅支持如下关键字:eq, in, gt, lt, not')
|
|
||||||
except AssertionError:
|
|
||||||
logger.error(
|
|
||||||
f"第{index}个响应断言失败 -|- 预期结果: {_k}: {_v}, {type(_v)} {k} 实际结果: {actual}, {type(actual)}")
|
|
||||||
allure_step(
|
|
||||||
step_title=f'第{index}个响应断言失败---->预期结果: {_k}: {_v}, {type(_v)} {k} 实际结果: {actual}, {type(actual)}')
|
|
||||||
logger.info('\n-------------End:响应断言--------------------\n' \
|
|
||||||
"=====================================================")
|
|
||||||
raise AssertionError(
|
|
||||||
f"第{index}个响应断言失败 -|- 预期结果: {_k}: {_v}, {type(_v)} {k} 实际结果: {actual}, {type(actual)}")
|
|
||||||
|
|
||||||
logger.info('\n-------------End:响应断言--------------------\n' \
|
|
||||||
"=====================================================")
|
|
||||||
|
|
||||||
|
|
||||||
@allure.step("数据库断言")
|
|
||||||
def assert_sql(db_info, expected: dict):
|
|
||||||
"""
|
|
||||||
数据库断言
|
|
||||||
:param db_info: 数据库配置信息
|
|
||||||
:param expected: 预期结果,从excel中或者yaml读取、或者手动传入,格式如下:
|
|
||||||
{
|
|
||||||
'eq':
|
|
||||||
{'sql': 'select count(*) from users where user_id=1;', 'len': '1'},
|
|
||||||
'eq':
|
|
||||||
{'sql': 'select * from users where user_id=1;', '$.username': '${username}'},
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
logger.info("\n======================================================\n" \
|
|
||||||
f"-------------Start:数据库断言--------------------")
|
|
||||||
if expected is None:
|
|
||||||
logger.info("判断是否存在数据库断言---->当前用例无数据库断言")
|
|
||||||
allure_step(step_title='判断是否存在数据库断言---->当前用例无数据库断言')
|
|
||||||
return
|
|
||||||
if not db_info:
|
|
||||||
logger.error("判断是否存在数据库配置---->当前环境无数据库配置,跳过数据库断言!")
|
|
||||||
allure_step(step_title='判断是否存在数据库配置---->当前环境无数据库配置,跳过数据库断言!')
|
|
||||||
logger.info('\n-------------End:数据库断言--------------------\n'
|
|
||||||
"=====================================================")
|
|
||||||
return
|
|
||||||
for k, v in expected.items():
|
|
||||||
sql_result = None
|
|
||||||
for _k, _v in v.items():
|
|
||||||
if _k == "sql":
|
|
||||||
try:
|
|
||||||
# 查询数据库,获取查询结果
|
|
||||||
sql_result = MysqlServer(**db_info).query_one(_v)
|
|
||||||
logger.info(f'数据库响应断言 -|- SQL:{_v} || 查询结果:{sql_result}')
|
|
||||||
allure_step(step_title=f'数据库断言---->SQL:{_v} ',
|
|
||||||
content=f'查询结果:{sql_result}')
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f'数据库服务报错:{e}')
|
|
||||||
allure_step(step_title=f'数据库服务报错',
|
|
||||||
content=f'{e} ')
|
|
||||||
logger.info('\n-------------End:数据库断言--------------------\n'
|
|
||||||
"=====================================================")
|
|
||||||
raise AssertionError(f"数据库服务报错:{e}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if k == "eq": # 预期结果 = 实际结果
|
|
||||||
if _k == "len":
|
|
||||||
assert _v == len(sql_result)
|
|
||||||
logger.success(f"预期结果: {_v} == 实际结果: {len(sql_result)}, 断言成功!")
|
|
||||||
allure_step(
|
|
||||||
step_title=f'数据库断言结果---->预期结果: {_v} == 实际结果: {len(sql_result)}, 断言成功!')
|
|
||||||
# 如果时$.开头,则从数据库查询结果中提取相应的值作为实际结果
|
|
||||||
elif _k.startswith("$."):
|
|
||||||
actual = json_extractor(sql_result, _k)
|
|
||||||
assert _v == actual
|
|
||||||
logger.success(f"预期结果: {_v} == 实际结果: {actual}, 断言成功!")
|
|
||||||
allure_step(step_title=f'数据库断言结果---->预期结果: {_v} == 实际结果: {actual}, 断言成功!')
|
|
||||||
except AssertionError:
|
|
||||||
logger.error(f"数据库断言失败 -|- 预期结果:{_v} {k} 实际结果: {sql_result})")
|
|
||||||
allure_step(step_title=f'数据库断言失败---->预期结果:{_v} {k} 实际结果: {sql_result}')
|
|
||||||
logger.info('\n-------------End:数据库断言--------------------\n' \
|
|
||||||
"=====================================================")
|
|
||||||
raise AssertionError(f"数据库断言失败 -|-预期结果: {_v} {k} 实际结果: {sql_result}")
|
|
||||||
|
|
||||||
logger.info('\n-------------End:数据库断言--------------------\n' \
|
|
||||||
"=====================================================")
|
|
||||||
|
|
||||||
|
|
||||||
def response_type(response: Response) -> str:
|
|
||||||
"""
|
|
||||||
:param response: requests 返回
|
|
||||||
:return: 返回响应数据类型 json或者str
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
response.json()
|
|
||||||
return "json"
|
|
||||||
except:
|
|
||||||
return "str"
|
|
||||||
@@ -279,10 +279,10 @@ def after_request_extract(response: Response, extract):
|
|||||||
re_result = {}
|
re_result = {}
|
||||||
response_result = {}
|
response_result = {}
|
||||||
if extract:
|
if extract:
|
||||||
if extract.get("type_json"):
|
if extract.get("type_jsonpath"):
|
||||||
# 如果响应数据是json格式,则将按照json方式对后置提取参数进行处理
|
# 如果响应数据是json格式,则将按照json方式对后置提取参数进行处理
|
||||||
res = response.json()
|
res = response.json()
|
||||||
for k, v in extract["type_json"].items():
|
for k, v in extract["type_jsonpath"].items():
|
||||||
json_result[k] = json_extractor(res, v)
|
json_result[k] = json_extractor(res, v)
|
||||||
logger.debug("\n-------------从response.json()中通过jsonpath方式提取到的结果--------------------\n"
|
logger.debug("\n-------------从response.json()中通过jsonpath方式提取到的结果--------------------\n"
|
||||||
f"后置提取参数(新): {json_result}\n")
|
f"后置提取参数(新): {json_result}\n")
|
||||||
@@ -290,7 +290,7 @@ def after_request_extract(response: Response, extract):
|
|||||||
# 如果响应数据是str格式,则将按照str方式对后置提取参数进行处理
|
# 如果响应数据是str格式,则将按照str方式对后置提取参数进行处理
|
||||||
res = response.text
|
res = response.text
|
||||||
for k, v in extract["type_re"].items():
|
for k, v in extract["type_re"].items():
|
||||||
re_result[k] = re_extract(res, v)
|
re_result[k] = data_handle(obj=re_extract(res, v))
|
||||||
logger.debug("\n-------------从response.text中通过正则表达式提取到的结果--------------------\n"
|
logger.debug("\n-------------从response.text中通过正则表达式提取到的结果--------------------\n"
|
||||||
f"后置提取参数(新): {re_result}\n")
|
f"后置提取参数(新): {re_result}\n")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user