Commit fbf30baf by mwx

api test v1

parent 26d1de24
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: __init__.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:02
# Description:
#
# ---------------------------------------------------------------------------
import functools
import logging
import os
import time
"""
每个用户有独立的数据配置层,每个用户有自己的配置文件-config.ini--->这个文件名称不能改变文件名---需要单独读取---ReadUserIni
接口自动化框架有公共的配置文件---config.ini--->这个文件名称不能改变文件名---ReadBasicIni
在用例层-每个用户都有自己独立的测试层--用户的测试层中的__init__.py文件中需要配置用户存放数据的目录名称
"""
"""
pytest:
报告插件:pytest-html ---> pip install pytest-html
输出报告的命令:pytest -vv 用例模块/用例包 --html=报告存放的路径 --self-contained-html
失败重跑插件:pytest-rerunfailures --> pip install pytest-rerunfailures
输出报告的命令:pytest -vv 用例模块/用例包 --html=报告存放的路径 --self-contained-html --reruns 失败重跑的次数 --reruns-delay 失败重跑间隔时间
allure生成报告:
需要下pytest的插件:pip install allure-pytest
1:使用pytest执行用例,生成执行结果的json文件
pytest 用例模块/用例包 --alluredir=存放json文件的目录 --clean-alluredir --reruns 失败重跑的次数 --reruns-delay 失败重跑间隔时间
2: 根据第一步测试结果的json文件生成报告。
i: 本地生成测试报告服务
allure serve 第一步存放测试结果json文件的目录
ii: 生成静态的报告
allure generate 第一步存放测试结果json文件的目录 -o 静态报告存放的目录
打开静态的报告:allure open 静态报告存放的目录
"""
def api_log():
# 创建Logger对象 root
logger = logging.getLogger()
# 设置级别
logger.level = logging.INFO
# 日志文件存放的路径
log_dir_path = os.path.join(os.path.join(os.path.dirname(__file__), "report"), "log")
log_file_name = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + ".log"
log_path = os.path.join(log_dir_path, log_file_name)
# 创建日志文件的Handler对象
handler = logging.FileHandler(filename=log_path, mode="a", encoding="utf-8")
# 设置日志显示的格式
# formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s')
# 给Handler设置日志的格式
handler.setFormatter(formatter)
# 给Logger添加Handler对象
logger.addHandler(handler)
return logger
log = api_log()
def logor(func_name):
@functools.wraps(func_name) # 装饰器返回内部函数时,沿用被装饰者的名称
def inner(*args, **kwargs):
# __code__: 返回函数或方法的对象,可以获取函数或者方法的属性。
# __code__.co_filename:获取函数或方法所在的py文件
# __code__.co_firstlineno:获取函数或方法所在的py文件开始行号
log.info(f"执行的功能为:{func_name.__name__}, 功能描述为:{func_name.__doc__}, 所在的py文件为:{func_name.__code__.co_filename}, 所在的行为:{func_name.__code__.co_firstlineno}行,形参的值为:{args}或{kwargs}")
try:
result = func_name(*args, **kwargs)
except Exception as e:
log.error(f"执行的功能为:{func_name.__name__}, 功能描述为:{func_name.__doc__}, 所在的py文件为:{func_name.__code__.co_filename}, 所在的行为:{func_name.__code__.co_firstlineno}行,形参的值为:{args}或{kwargs}"
f"错误的类型为:{type(e)}, 错误的描述为:{e}")
raise e
else:
return result
return inner
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: __init__.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:02
# Description:
#
# ---------------------------------------------------------------------------
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: db.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:22
# Description:
#
# ---------------------------------------------------------------------------
import pymysql
from APIAutoTest_v3_1 import log
from APIAutoTest_v3_1.common.read_basic_ini import ReadBasicIni
class DB:
def __init__(self):
"""链接数据库"""
ini = ReadBasicIni()
try:
self.conn = pymysql.connect(
host=ini.get_connect_sql_msg("host"),
port=int(ini.get_connect_sql_msg("port")),
user=ini.get_connect_sql_msg("user"),
password=ini.get_connect_sql_msg("pwd"),
database=ini.get_connect_sql_msg("database"),
charset="utf8"
)
self.cursor = self.conn.cursor()
except Exception as e:
log.error(f"链接数据库时出错,请求检查ini配置文件,发生的错误类型为:{type(e)}, 错误的描述为:{e}")
raise e
def delete(self, sql_sentence):
"""执行删除的sql语句"""
log.info(f"执行的方法为:delete,功能描述为:执行删除的sql语句,传入的参数为:{sql_sentence}")
if isinstance(sql_sentence, str) and sql_sentence.strip().lower().startswith("delete"):
try:
self.cursor.execute(sql_sentence)
self.conn.commit()
except Exception as e:
log.error(
f"执行的方法为:delete,功能描述为:执行删除的sql语句,传入的参数为:{sql_sentence}, 错误类型为:{type(e)}, 错误的描述为:{e}")
raise e
else:
log.error(f"执行的方法为:delete,功能描述为:执行删除的sql语句,传入的参数为:{sql_sentence}, 错误类型为:ValueError, 错误的描述为:sql语句错误")
raise ValueError("sql语句错误")
def select(self, sql_sentence):
"""执行查询的sql语句"""
log.info(f"执行的方法为:select,功能描述为:执行查询的sql语句,传入的参数为:{sql_sentence}")
if isinstance(sql_sentence, str) and sql_sentence.strip().lower().startswith("select"):
try:
self.cursor.execute(sql_sentence)
except Exception as e:
log.error(
f"执行的方法为:select,功能描述为:执行查询的sql语句,传入的参数为:{sql_sentence}, 错误类型为:{type(e)}, 错误的描述为:{e}")
raise e
else:
# 接收查询的结果
select_result = self.cursor.fetchall()
# 判断查询结果是否为空的元组,如果为空的元组返回None,如果不为空的元组返回查询的第一个结果
if select_result:
return select_result[0][0]
else:
log.error(f"执行的方法为:select,功能描述为:执行查询的sql语句,传入的参数为:{sql_sentence}, 错误类型为:ValueError, 错误的描述为:sql语句错误")
raise ValueError("sql语句错误")
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: read_ini.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:09
# Description:
#
# ---------------------------------------------------------------------------
import configparser
import os
from APIAutoTest_v3_1 import log
class ReadBasicIni:
def __init__(self):
"""读取ini文件"""
self.data_config = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data_config")
ini_path = os.path.join(self.data_config, "basic_config.ini")
self.conf = configparser.ConfigParser()
self.conf.read(ini_path, encoding="utf-8")
def get_host(self, key):
"""根据key获取host节点下被测系统的域名"""
log.info(f"执行get_host方法,功能为:根据key获取host节点下被测系统的域名, 传入的参数为:{key}")
try:
return self.conf.get("host", key)
except Exception as e:
log.info(f"执行get_host方法,功能为:根据key获取host节点下被测系统的域名, 传入的参数为:{key}"
f"发生错误,错误的类型为:{type(e)}, 错误的描述为:{e}")
raise e
def get_connect_sql_msg(self, key):
"""根据key获取sql节点下数据库的链接信息"""
log.info(f"执行get_connect_sql_msg方法,功能为:根据key获取sql节点下数据库的链接信息, 传入的参数为:{key}")
try:
return self.conf.get("sql", key)
except Exception as e:
log.info(f"执行get_connect_sql_msg方法,功能为:根据key获取sql节点下数据库的链接信息, 传入的参数为:{key}"
f"发生错误,错误的类型为:{type(e)}, 错误的描述为:{e}")
raise e
if __name__ == '__main__':
ini = ReadBasicIni()
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: read_excel.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:34
# Description:
#
# ---------------------------------------------------------------------------
import openpyxl
from APIAutoTest_v3_1 import log, logor
from APIAutoTest_v3_1.common.read_basic_ini import ReadBasicIni
from APIAutoTest_v3_1.common.read_user_ini import ReadUserIni
from APIAutoTest_v3_1.common.read_json import read_json
from APIAutoTest_v3_1.data_config.settings import *
class ReadExcel:
@logor
def __init__(self, user):
"""读取所有的json文件,获取excel的工作簿和工作表"""
# 读取指定用户的ini文件
ini = ReadUserIni(user)
# 获取指定用户的存放数据的json文件路径
case_data_path = ini.get_file_path(CASE_NAME)
expect_data_path = ini.get_file_path(EXPECT_NAME)
sql_data_path = ini.get_file_path(SQL_NAME)
self.case_data_dict = read_json(case_data_path)
self.expect_data_dict = read_json(expect_data_path)
self.sql_data_dict = read_json(sql_data_path)
# 获取指定用户的excel文件路径
excel_path = ini.get_file_path(EXCEL_NAME)
# 获取指定用户的工作表名称
table_name = ini.get_table_name(TABLE_NAME)
try:
wb = openpyxl.load_workbook(excel_path)
self.ws = wb[table_name]
except Exception as e:
# log.error(f"获取excel的工作簿或工作表出错,excel的路径为:{excel_path},工作表名称为:{table_name},错误的类型为:{type(e)}, 错误的描述为:{e}")
raise e
@logor
def __get_cell_value(self, column: str, row: int) -> str:
"""获取指定单元格数据"""
# log.info(f"执行的方法为:__get_cell_value,功能描述为:获取指定单元格数据,传入的参数为:{column}列,{row}行")
try:
cell_value = self.ws[column+str(row)].value
except Exception as e:
# log.error(f"执行的方法为:__get_cell_value,功能描述为:获取指定单元格数据,传入的参数为:{column}列,{row}行, 报错,错误类型为:{type(e)}, 错误的描述为:{e}")
raise e
else:
if cell_value is None:
return None
elif cell_value.strip():
return cell_value.strip()
@logor
def module_name(self, row):
"""根据行号,获取模块名称"""
# log.info(f"执行的方法为:module_name,功能描述为:根据行号,获取模块名称,传入的参数为:{row}行")
return self.__get_cell_value(MODULE, row)
@logor
def api_name(self, row):
"""根据行号,获取接口名称"""
# log.info(f"执行的方法为:api_name,功能描述为:根据行号,获取接口名称,传入的参数为:{row}行")
return self.__get_cell_value(API, row)
@logor
def req_method(self, row):
"""根据行号,获取用例的请求方法"""
# log.info(f"执行的方法为:req_method,功能描述为:根据行号,获取用例的请求方法,传入的参数为:{row}行")
return self.__get_cell_value(METHOD, row)
@logor
def req_url(self, row):
"""根据行号,获取用例的请求url"""
# log.info(f"执行的方法为:req_url,功能描述为:根据行号,获取用例的请求url,传入的参数为:{row}行")
path = self.__get_cell_value(PATH, row)
if path:
# 创建ReadBasicIni类对象,调用get_host获取被测系统的域名,在和接口路径拼接完整的url
return ReadBasicIni().get_host(HOST_NAME) + path
@logor
def req_mime(self, row):
"""根据行号,获取用例的请求媒体类型"""
# log.info(f"执行的方法为:req_mime,功能描述为:根据行号,获取用例的请求媒体类型,传入的参数为:{row}行")
mime = self.__get_cell_value(MIME, row)
if mime:
return mime.lower()
@logor
def case_data(self, row):
"""根据行号,获取用例数据"""
# log.info(f"执行的方法为:case_data,功能描述为:根据行号,获取用例数据,传入的参数为:{row}行")
case_data_key = self.__get_cell_value(CASE, row)
module_name = self.module_name(row)
api_name = self.api_name(row)
if case_data_key and module_name and api_name:
return self.case_data_dict[module_name][api_name][case_data_key]
@logor
def expect_data(self, row):
"""根据行号,获取期望数据"""
# log.info(f"执行的方法为:expect_data,功能描述为:根据行号,获取期望数据,传入的参数为:{row}行")
expect_data_key = self.__get_cell_value(EXPECT, row)
module_name = self.module_name(row)
api_name = self.api_name(row)
if expect_data_key and module_name and api_name:
return self.expect_data_dict[module_name][api_name][expect_data_key]
@logor
def sql_type(self, row):
"""根据行号,获取sql语句类型"""
# log.info(f"执行的方法为:sql_type,功能描述为:根据行号,获取sql语句类型,传入的参数为:{row}行")
value = self.__get_cell_value(SQLTYPE, row)
if value:
return value.lower()
@logor
def sql_data(self, row):
"""根据行号,获取sql语句"""
# log.info(f"执行的方法为:sql_data,功能描述为:根据行号,获取sql语句,传入的参数为:{row}行")
sql_data_key = self.__get_cell_value(SQL, row)
module_name = self.module_name(row)
api_name = self.api_name(row)
if sql_data_key and module_name and api_name:
return self.sql_data_dict[module_name][api_name][sql_data_key]
@logor
def update_key(self, row):
"""根据行号,获取用例的更新的key"""
# log.info(f"执行的方法为:update_key,功能描述为:根据行号,获取用例的更新的key,传入的参数为:{row}行")
return self.__get_cell_value(UPDATEKEY, row)
@logor
def case_title(self, row):
"""根据行,获取用例的标题"""
return self.__get_cell_value(TITLE, row)
@logor
def case_level(self, row):
"""根据行获取用例的等级"""
return self.__get_cell_value(LEVEL, row)
@logor
def get_data(self):
"""将测试使用到的数据存放在二维列表中"""
list_data = []
for row in range(2, self.ws.max_row+1):
module = self.module_name(row)
api = self.api_name(row)
title = self.case_title(row)
level = self.case_level(row)
method = self.req_method(row)
url = self.req_url(row)
mime = self.req_mime(row)
case_data = self.case_data(row)
expect_data = self.expect_data(row)
sql_type = self.sql_type(row)
sql_data = self.sql_data(row)
update_key = self.update_key(row)
if method and url and expect_data:
list_data.append([module, api, title, level, method, url, mime, case_data, expect_data, sql_type, sql_data, update_key])
else:
return list_data
if __name__ == '__main__':
excel = ReadExcel("zs_data")
print(excel.get_data())
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: read_json.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:17
# Description:
#
# ---------------------------------------------------------------------------
import json
import os
from APIAutoTest_v3_1 import log
def read_json(file_path):
"""读取json文件,将json文件的内容序列化为python对象在返回"""
log.info(f"执行的函数为:read_json,描述为:读取json文件,将json文件的内容序列化为python对象在返回,传入的参数为:{file_path}")
if isinstance(file_path, str) and os.path.isfile(file_path) and file_path.endswith(".json"):
with open(file_path, mode="r", encoding="utf-8") as f:
try:
return json.loads(f.read())
except Exception as e:
log.error(
f"执行的函数为:read_json,描述为:读取json文件,将json文件的内容序列化为python对象在返回,传入的参数为:{file_path}, 文件路径不存在"
f"错误类型为:{type(e)}, 错误的描述为:{e}")
raise e
else:
log.error(f"执行的函数为:read_json,描述为:读取json文件,将json文件的内容序列化为python对象在返回,传入的参数为:{file_path}, 文件路径不存在"
f"错误类型为:FileNotFoundError, 错误的描述为:文件路径错误")
raise FileNotFoundError("文件路径错误")
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: read_ini.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:09
# Description:
#
# ---------------------------------------------------------------------------
import configparser
import os
from APIAutoTest_v3_1 import log, logor
class ReadUserIni:
def __init__(self, user):
"""读取ini文件, 需要指明读取哪个用户的ini配置文件"""
# 获取数据配置层的路径
data_config = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data_config")
# 获取用户存放数据的目录路径
self.user_data_config = os.path.join(data_config, user)
# 获取用户的ini文件路径
ini_path = os.path.join(self.user_data_config, "config.ini")
self.conf = configparser.ConfigParser()
res = self.conf.read(ini_path, encoding="utf-8")
if not res:
log.error(f"读取ini文件失败,请检查用户名是否传入正确,用户名为:{user}, 对应的ini文件路径为:{ini_path}")
raise ValueError("用户名错误")
@logor
def get_file_path(self, key):
"""根据key获取file节点下文件的路径"""
# log.info(f"执行get_file_path方法,功能为:根据key获取file节点下文件的路径, 传入的参数为:{key}")
try:
file_name = self.conf.get("file", key)
except Exception as e:
# log.info(f"执行get_file_path方法,功能为:根据key获取file节点下文件的路径, 传入的参数为:{key}"
# f"发生错误,错误的类型为:{type(e)}, 错误的描述为:{e}")
raise e
else:
return os.path.join(self.user_data_config, file_name)
@logor
def get_table_name(self, key):
"""根据key获取table节点下工作表的名称"""
try:
return self.conf.get("table", key)
except Exception as e:
raise e
if __name__ == '__main__':
ini = ReadUserIni("zs_data")
print(ini.get_table_name("name"))
# 配置被测系统的域名
[host]
bpm=http://36.139.193.99:8088
# 数据库的链接信息
[sql]
host=36.139.193.99
port=3306
user=root
pwd=Rhrc@2024
database=eip8
\ No newline at end of file
{
"认证接口": {
"登录系统": {
"LoginSuccess": {
"username": "admin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorPasswordIsError": {
"username": "admin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE"
},
"LoginErrorPasswordIsLong": {
"username": "admin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorPasswordIsShort": {
"username": "admin",
"password": "1"
},
"LoginErrorPasswordIsSpecial": {
"username": "admin",
"password": "㊚㊛囍㊒㊖☑✔☐☒✘卍"
},
"LoginErrorPasswordIsNone": {
"username": "admin",
"password": ""
},
"LoginErrorUsernameIsNone": {
"username": "",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorUsernameIsShort": {
"username": "a",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorUsernameIsLong": {
"username": "adminadminadminadminadminadmin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorUsernameIsSpecial": {
"username": "㊚㊛囍㊒㊖☑✔☐☒✘卍",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
}
}
},
"维度管理": {
"添加维度": {
"AddDemSuccess": {
"code": "testAddDem",
"description": "testAddDem",
"isDefault": 0,
"name": "testAddDem"
}
},
"根据维度编码删除维度": {
"DeleteDemSuccess": {"ids": "需要更新"},
"DeleteDemErrorIdIsNone": {"ids": ""}
},
"设置默认维度": {
"SetDefaultDemSuccess": {"code": "testAddDem"}
},
"更新维度": {
"UpdateDemSuccess": {
"code": "testAddDem",
"description": "测试添加的维度",
"isDefault": 0,
"name": "测试添加的维度"
}
}
},
"组织管理": {
"添加组织": {
"AddOrgSuccess": {
"code": "testAddOrg",
"demId": "需要更新",
"exceedLimitNum": 0,
"grade": "",
"limitNum": 0,
"name": "测试添加的组织",
"nowNum": 0,
"orderNo": 0,
"parentId": "0"
}
},
"删除组织": {
"DelOrgSuccess": "testAddOrg"
},
"保存组织参数": {
"SaveOrgParamSuccess": {
"query": {"orgCode": "testAddOrg"},
"body": [{"alias": "sz", "value": 1}]
}
}
},
"用户管理": {
"添加用户": {
"AddUserSuccess": {
"id":"",
"account":"testAddUser",
"address":"",
"email":"",
"fullname":"测试添加的用户",
"mobile":"",
"password":"123456",
"photo":"",
"sex":"",
"status":1
}
}
}
}
# 配置文件的名称
[file]
# 配置用例管理文件名称
excel=apiAutoTest.xlsx
# 配置用例json文件名称
case=case_data.json
# 配置期望数据名称名称
expect=expect_data.json
# 配置sql语句文件的名称
sql=sql_data.json
# 配置excel工作表的名称
[table]
name=BPM接口测试
{
"认证接口":{
"登录系统":{
"LoginSuccess":{"username":"超级管理员","account":"admin","userId":"1","expiration":86400,"loginStatus":true},
"LoginErrorPasswordIsError":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsLong":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsShort":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsSpecial":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsNone":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsNone":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsShort":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsLong":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsSpecial":{"state":false,"message":"账户错误或该租户未启用"}
},
"刷新token": {
"RefreshTokenSuccess": {"loginStatus":true}
}
},
"维度管理": {
"添加维度": {
"AddDemSuccess": {"state": true, "message": "添加维度成功!"}
},
"根据维度编码删除维度": {
"DeleteDemSuccess": {"state":true,"message":"删除维度成功!"},
"DeleteDemErrorIdIsNone": {"state":false,"message":"维度id不能为空!"}
},
"设置默认维度": {
"SetDefaultDemSuccess": {"state": true, "message": "设置默认维度成功"}
},
"更新维度": {
"UpdateDemSuccess": {"state": true, "message": "更新维度成功"}
}
},
"组织管理": {
"添加组织": {
"AddOrgSuccess": {"state": true, "message": "添加组织成功"}
},
"删除组织": {
"DelOrgSuccess": {"state":true,"message":"删除组织成功!"}
},
"保存组织参数": {
"SaveOrgParamSuccess": {"state":true,"message":"保存组织参数成功!"}
}
},
"用户管理": {
"添加用户": {
"AddUserSuccess": {"state":true,"message":"用户添加成功!","value":"","code":200}
}
}
}
{
"维度管理": {
"添加维度": {
"AddDemSuccess": "DELETE FROM uc_demension WHERE CODE_=\"testAddDem\";"
},
"根据维度编码删除维度": {
"DeleteDemSuccess": "SELECT ID_ FROM uc_demension WHERE CODE_=\"testAddDem\";"
}
},
"组织管理": {
"添加组织": {
"AddOrgSuccess": {
"delete": "delete from uc_org where CODE_=\"testAddOrg\";",
"select": "SELECT ID_ FROM uc_demension WHERE CODE_=\"testAddDem\";"
}
}
},
"用户管理": {
"添加用户": {
"AddUserSuccess": "delete from uc_user where ACCOUNT_=\"testAddUser\";"
}
}
}
\ No newline at end of file
# 配置文件的名称
[file]
# 配置用例管理文件名称
excel=用例excel文件.xlsx
# 配置用例json文件名称
case=用例数据.json
# 配置期望数据名称名称
expect=期望数据.json
# 配置sql语句文件的名称
sql=sql语句.json
# 配置excel工作表的名称
[table]
name=接口测试
{
{
"维度管理": {
"添加维度": {
"AddDemSuccess": "DELETE FROM uc_demension WHERE CODE_=\"testAddDem\";"
},
"根据维度编码删除维度": {
"DeleteDemSuccess": "SELECT ID_ FROM uc_demension WHERE CODE_=\"testAddDem\";"
}
},
"组织管理": {
"添加组织": {
"AddOrgSuccess": {
"delete": "delete from uc_org where CODE_=\"testAddOrg\";",
"select": "SELECT ID_ FROM uc_demension WHERE CODE_=\"testAddDem\";"
}
}
},
"用户管理": {
"添加用户": {
"AddUserSuccess": "delete from uc_user where ACCOUNT_=\"testAddUser\";"
}
}
}
\ No newline at end of file
{
{
"认证接口":{
"登录系统":{
"LoginSuccess":{"username":"超级管理员","account":"admin","userId":"1","expiration":86400,"loginStatus":true},
"LoginErrorPasswordIsError":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsLong":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsShort":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsSpecial":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorPasswordIsNone":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsNone":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsShort":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsLong":{"state":false,"message":"账户错误或该租户未启用"},
"LoginErrorUsernameIsSpecial":{"state":false,"message":"账户错误或该租户未启用"}
},
"刷新token": {
"RefreshTokenSuccess": {"loginStatus":true}
}
},
"维度管理": {
"添加维度": {
"AddDemSuccess": {"state": true, "message": "添加维度成功!"}
},
"根据维度编码删除维度": {
"DeleteDemSuccess": {"state":true,"message":"删除维度成功!"},
"DeleteDemErrorIdIsNone": {"state":false,"message":"维度id不能为空!"}
},
"设置默认维度": {
"SetDefaultDemSuccess": {"state": true, "message": "设置默认维度成功"}
},
"更新维度": {
"UpdateDemSuccess": {"state": true, "message": "更新维度成功"}
}
},
"组织管理": {
"添加组织": {
"AddOrgSuccess": {"state": true, "message": "添加组织成功"}
},
"删除组织": {
"DelOrgSuccess": {"state":true,"message":"删除组织成功!"}
},
"保存组织参数": {
"SaveOrgParamSuccess": {"state":true,"message":"保存组织参数成功!"}
}
},
"用户管理": {
"添加用户": {
"AddUserSuccess": {"state":true,"message":"用户添加成功!","value":"","code":200}
}
}
}
{
{
"认证接口": {
"登录系统": {
"LoginSuccess": {
"username": "admin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorPasswordIsError": {
"username": "admin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE"
},
"LoginErrorPasswordIsLong": {
"username": "admin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcwlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdcPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorPasswordIsShort": {
"username": "admin",
"password": "1"
},
"LoginErrorPasswordIsSpecial": {
"username": "admin",
"password": "㊚㊛囍㊒㊖☑✔☐☒✘卍"
},
"LoginErrorPasswordIsNone": {
"username": "admin",
"password": ""
},
"LoginErrorUsernameIsNone": {
"username": "",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorUsernameIsShort": {
"username": "a",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorUsernameIsLong": {
"username": "adminadminadminadminadminadmin",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
},
"LoginErrorUsernameIsSpecial": {
"username": "㊚㊛囍㊒㊖☑✔☐☒✘卍",
"password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKHT3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuKPGOMPPSLlZWLfvG7yzdc+CyE="
}
}
},
"维度管理": {
"添加维度": {
"AddDemSuccess": {
"code": "testAddDem",
"description": "testAddDem",
"isDefault": 0,
"name": "testAddDem"
}
},
"根据维度编码删除维度": {
"DeleteDemSuccess": {"ids": "需要更新"},
"DeleteDemErrorIdIsNone": {"ids": ""}
},
"设置默认维度": {
"SetDefaultDemSuccess": {"code": "testAddDem"}
},
"更新维度": {
"UpdateDemSuccess": {
"code": "testAddDem",
"description": "测试添加的维度",
"isDefault": 0,
"name": "测试添加的维度"
}
}
},
"组织管理": {
"添加组织": {
"AddOrgSuccess": {
"code": "testAddOrg",
"demId": "需要更新",
"exceedLimitNum": 0,
"grade": "",
"limitNum": 0,
"name": "测试添加的组织",
"nowNum": 0,
"orderNo": 0,
"parentId": "0"
}
},
"删除组织": {
"DelOrgSuccess": "testAddOrg"
},
"保存组织参数": {
"SaveOrgParamSuccess": {
"query": {"orgCode": "testAddOrg"},
"body": [{"alias": "sz", "value": 1}]
}
}
},
"用户管理": {
"添加用户": {
"AddUserSuccess": {
"id":"",
"account":"testAddUser",
"address":"",
"email":"",
"fullname":"测试添加的用户",
"mobile":"",
"password":"123456",
"photo":"",
"sex":"",
"status":1
}
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: __init__.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:02
# Description:
#
# ---------------------------------------------------------------------------
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: requests_method.py
# Author: lao_zhao
# Datetime: 2024/11/5 15:22
# Description:
#
# ---------------------------------------------------------------------------
import requests
from APIAutoTest_v3_1 import log
from APIAutoTest_v3_1.common.db import DB
from APIAutoTest_v3_1.common.read_basic_ini import ReadBasicIni
class RequestsMethod:
def __init__(self):
"""关联登录的状态"""
self.db = DB()
self.bpm_session = requests.sessions.Session()
login_url = ReadBasicIni().get_host("bpm") + "/auth"
login_data = {"username": "admin", "password": "QKpkgIwnyACrUXQrQVRaSUbhPSOsKnj5hoRN5LGKH"
"T3NmLCwGIN+aGa981YZtlLoEfpVvGDnILn0QU09VHSkgL+Or6"
"oTG1E8OtVTmAEjivAe/a4zLPwd/OfvKYIYbBP5ExZyy+wlVTbLgDfqkuK"
"PGOMPPSLlZWLfvG7yzdc+CyE="}
token = self.bpm_session.post(url=login_url, json=login_data).json().get("token")
self.bpm_session.headers.update({"Authorization": f"Bearer {token}"})
def request_all(self, req_method, req_url, req_mime=None, req_case_data=None, sql_type=None, sql_data=None, update_key=None):
"""
封装公共的请求方法
:param req_method: 请求方法
:param req_url: 请求url
:param req_mime: 请求的媒体类型
:param req_case_data: 请求数据
:param sql_type: sql语句类型
:param sql_data: sql语句
:param update_key: 更新的key
:return: Response type
"""
# 判断sql语句类型是否为delete,如果是,先执行删除的sql语句,执行完之后再发送请求
if sql_type == "delete":
self.db.delete(sql_data)
# 判断sql语句类型是否为select,如果是,先执行查询的sql语句,并获取查询的结果,并将查询结果更新到用例数据中,再发送请求
elif sql_type == "select":
select_result = self.db.select(sql_data)
req_case_data[update_key] = select_result
# 判断sql语句类型是否为:select|delete or delete|select,如果是,同时执行查询和删除
elif sql_type == "select|delete" or sql_type == "delete|select":
# 要执行删除--删除数据库中现有的数据
self.db.delete(sql_data["delete"])
# 查询语句--将查询结果更新到用例数据中
select_result = self.db.select(sql_data["select"])
req_case_data[update_key] = select_result
# 上面的分支执行完之后,该删除的数据已经删除,该更新用例数据的已更新,下面就可以发送请求
# 发送请求时,用例使用使用哪些关键字传参,需要添加判断
# 判断媒体类型的值是否为:application/json 或 json,使用json传参
if req_mime == "json" or req_mime == "application/json":
return self.bpm_session.request(method=req_method, url=req_url, json=req_case_data)
# 判断媒体类型的值是否为:application/x-www-form-urlencoded 、x-www-form-urlencoded 或 form,使用data传参
# 除了上传文件和json类型传参以外的任何方式在请求体中传参,都使用data传参。
elif req_mime == "application/x-www-form-urlencoded" or req_mime == "x-www-form-urlencoded" or req_mime == "form":
return self.bpm_session.request(method=req_method, url=req_url, data=req_case_data)
# 判断媒体类型的值是否为:multipart/form-data 或 form-data,使用files传参
elif req_mime == "multipart/form-data" or req_mime == "form-data":
return self.bpm_session.request(method=req_method, url=req_url, files=req_case_data)
# 判断媒体类型的值是否为:query,使用params传参
elif req_mime == "query":
return self.bpm_session.request(method=req_method, url=req_url, params=req_case_data)
# 判断媒体类型的值是否为:None,表示没有传参
elif req_mime is None:
return self.bpm_session.request(method=req_method, url=req_url)
# 判断请求的媒体类型是否为:query|json 或者 json|query 如果是,表示在地址栏和请求体中同时传参,请求体还是以json类型传参。
elif req_mime == "query|json" or req_mime == "json|query":
# requests可以在地址栏和请求体同时传参,请求体传参根据不同的媒体类型选择不同关键字传参,地址使用params
return self.bpm_session.request(method=req_method, url=req_url, params=req_case_data["query"], json=req_case_data["body"])
else:
log.error(f"方法:request_all,传入的媒体类型为:{req_mime}, 没有符合的判断分支,可以自行封装,也可以继承之后覆盖")
raise ValueError("没有符合的判断分支,可以自行封装,也可以继承之后覆盖")
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: __init__.py
# Author: lao_zhao
# Datetime: 2024/11/5 14:02
# Description:
#
# ---------------------------------------------------------------------------
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: __init__.py
# Author: lao_zhao
# Datetime: 2024/11/5 17:35
# Description:
#
# ---------------------------------------------------------------------------
# USERNAME的值一定是用户数据配置层目录的名称
USERNAME = 'demo_data'
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: conftest.py
# Author: lao_zhao
# Datetime: 2024/11/5 15:40
# Description:
#
# ---------------------------------------------------------------------------
import pytest
from APIAutoTest_v3_1.requests_method.requests_method import RequestsMethod
@pytest.fixture(scope="session")
def req_fix():
"""创建RequestsMethod类型对象"""
req = RequestsMethod()
yield req
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: test_bpm.py
# Author: lao_zhao
# Datetime: 2024/11/5 15:39
# Description:
#
# ---------------------------------------------------------------------------
import pytest
import allure
from APIAutoTest_v3_1 import log
from APIAutoTest_v3_1.common.read_excel import ReadExcel
from APIAutoTest_v3_1.test_case.test_demo import USERNAME
class TestBPM:
@allure.epic("BPM_DEMO")
# @allure.feature("模块名称")
# @allure.story("接口名称")
# @allure.title("用例标题")
# @allure.severity("用例等级")
@pytest.mark.parametrize("module, api, title, level, method, url, mime, case_data, expect_data, sql_type, sql_data, update_key", ReadExcel(USERNAME).get_data())
def test_bpm(self, req_fix, module, api, title, level, method, url, mime, case_data, expect_data, sql_type, sql_data, update_key):
allure.dynamic.feature(module)
allure.dynamic.story(api)
allure.dynamic.title(title)
allure.dynamic.severity(level)
# 使用RequestsMethod类对象调用request_all方法发送请求。自定义固件就是RequestsMethod类对象
res = req_fix.request_all(req_method=method, req_url=url, req_mime=mime, req_case_data=case_data, sql_type=sql_type, sql_data=sql_data, update_key=update_key)
# 断言
try:
for key in expect_data.keys():
assert res.json().get(key) == expect_data[key]
except AssertionError:
log.error(f"断言失败,用例数据为:{case_data},期望数据为:{expect_data}, 服务器返回的数据为:{res.text}")
raise AssertionError("断言失败")
else:
log.info(f"断言成功,用例数据为:{case_data},期望数据为:{expect_data}, 服务器返回的数据为:{res.text}")
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: __init__.py
# Author: lao_zhao
# Datetime: 2024/11/5 17:35
# Description:
#
# ---------------------------------------------------------------------------
# USERNAME的值一定是用户数据配置层目录的名称
USERNAME = 'zs_data'
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: conftest.py
# Author: lao_zhao
# Datetime: 2024/11/5 15:40
# Description:
#
# ---------------------------------------------------------------------------
import pytest
from APIAutoTest_v3_1.requests_method.requests_method import RequestsMethod
@pytest.fixture(scope="session")
def req_fix():
"""创建RequestsMethod类型对象"""
req = RequestsMethod()
yield req
\ No newline at end of file
# -*-coding:utf-8 -*- #
# ---------------------------------------------------------------------------
# ProjectName: test63
# FileName: test_bpm.py
# Author: lao_zhao
# Datetime: 2024/11/5 15:39
# Description:
#
# ---------------------------------------------------------------------------
import allure
import pytest
from APIAutoTest_v3_1 import log
from APIAutoTest_v3_1.common.read_excel import ReadExcel
from APIAutoTest_v3_1.test_case.test_zs import USERNAME
class TestBPM:
@allure.epic("BPM_张三")
# @allure.feature("模块名称")
# @allure.story("接口名称")
# @allure.title("用例标题")
# @allure.severity("用例等级")
@pytest.mark.parametrize("module, api, title, level, method, url, mime, case_data, expect_data, sql_type, sql_data, update_key", ReadExcel(USERNAME).get_data())
def test_bpm(self, req_fix, module, api, title, level, method, url, mime, case_data, expect_data, sql_type, sql_data, update_key):
allure.dynamic.feature(module)
allure.dynamic.story(api)
allure.dynamic.title(title)
allure.dynamic.severity(level)
# 使用RequestsMethod类对象调用request_all方法发送请求。自定义固件就是RequestsMethod类对象
res = req_fix.request_all(req_method=method, req_url=url, req_mime=mime, req_case_data=case_data, sql_type=sql_type, sql_data=sql_data, update_key=update_key)
# 断言
try:
for key in expect_data.keys():
assert res.json().get(key) == expect_data[key]
except AssertionError:
log.error(f"断言失败,用例数据为:{case_data},期望数据为:{expect_data}, 服务器返回的数据为:{res.text}")
raise AssertionError("断言失败")
else:
log.info(f"断言成功,用例数据为:{case_data},期望数据为:{expect_data}, 服务器返回的数据为:{res.text}")
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment