class DB: mysql = ReadFile.read_config('$.database') def __init__(self): """初始化连接Mysql""" self.connection = pymysql.connect( host=self.mysql.get('host', 'localhost'), port=self.mysql.get('port', 3306), user=self.mysql.get('user', 'root'), password=self.mysql.get('password', '123456'), db=self.mysql.get('db_name', 'test'), charset=self.mysql.get('charset', 'utf8mb4'), cursorclass=pymysql.cursors.DictCursor ) def fetch_one(self, sql: str) -> object: """查询数据,查一条""" with self.connection.cursor() as cursor: cursor.execute(sql) result = cursor.fetchone() # 使用commit解决查询数据出现概率查错问题 self.connection.commit() return result def close(self): """关闭数据库连接""" self.connection.close()
class BaseRequest(object): session = None @classmethod def get_session(cls): if cls.session is None: cls.session = requests.Session() return cls.session @classmethod def send_request(cls, case: list, env: str = 'dev') -> object: """处理case数据,转换成可用数据发送请求 :param case: 读取出来的每一行用例内容,可进行解包 :param env: 环境名称 默认使用config.yaml server下的 dev 后面的基准地址 return: 响应结果, 预期结果 """ case_number, case_title, path, token, method, parametric_key, file_obj, data, sql, expect, is_save = case logger.debug( f"用例进行处理前数据: \n 接口路径: {path} \n 请求参数: {data} \n 后置sql: {sql} \n 预期结果: {expect} \n 保存响应: {is_save}" ) # allure报告 用例标题 allure_title(case_title) # 处理url、header、data、file、的前置方法 url = ReadFile.read_config( f'$.server.{env}') + DataProcess.handle_path(path) allure_step('请求地址', url) header = DataProcess.handle_header(token) allure_step('请求头', header) data = DataProcess.handle_data(data) allure_step('请求参数', data) file = DataProcess.handler_files(file_obj) allure_step('上传文件', file_obj) # 发送请求 res = cls.send_api(url, method, parametric_key, header, data, file) allure_step('响应耗时(s)', res.elapsed.total_seconds()) allure_step('响应内容', res.json()) # 响应后操作 if token == '写': DataProcess.have_token['Authorization'] = extractor( res.json(), ReadFile.read_config('$.expr.token')) allure_step('请求头中添加Token', DataProcess.have_token) # 保存用例的实际响应 if is_save == "是": DataProcess.save_response(case_number, res.json()) allure_step('存储实际响应', DataProcess.response_dict) return res.json(), expect, sql
class DataClearing: settings = ReadFile.read_config('$.database') server_settings = settings.get('ssh_server') server = None # 导出的sql文件名称及后缀 file_name = f"{settings.get('db_name')}_{datetime.now().strftime('%Y-%m-%dT%H_%M_%S')}.sql" @classmethod def server_init(cls, settings=settings, server_settings=server_settings): cls.server = ServerTools( host=settings.get('host'), port=server_settings.get('port'), username=server_settings.get('username'), password=server_settings.get('password'), private_key_file=server_settings.get('private_key_file'), privat_passowrd=server_settings.get('privat_passowrd')) # 新建backup_sql文件夹在服务器上,存放导出的sql文件 cls.server.execute_cmd("mkdir backup_sql") @classmethod def backup_mysql(cls): """ 备份数据库, 会分别备份在数据库所在服务器的/root/backup_sql/目录下, 与当前项目文件目录下的 backup_sqls 每次备份生成一个数据库名_当前年_月_日T_时_分_秒, 支持linux 服务器上安装的mysql服务(本人未调试),以及linux中docker部署的mysql备份 """ if cls.server_settings.get('mysql_container') is None: cmd = f"mysqldump -h127.0.0.1 -u{cls.settings.get('username')} -p{cls.settings.get('password')} {cls.settings.get('db_name')} > {cls.file_name}" else: # 将mysql服务的容器中的指定数据库导出, 参考文章 # https://www.cnblogs.com/wangsongbai/p/12666368.html cmd = f"docker exec -i {cls.server_settings.get('mysql_container')} mysqldump -h127.0.0.1 -u{cls.settings.get('user')} -p{cls.settings.get('password')} {cls.settings.get('db_name')} > /root/backup_sql/{cls.file_name}" cls.server.execute_cmd(cmd) cls.server.files_action(0, f"{cls.server_settings.get('sql_data_file')}", f"/root/backup_sql/{cls.file_name}") @classmethod def recovery_mysql(cls, sql_file: str = file_name, database: str = settings.get('db_name')): """ 恢复数据库, 从服务器位置(/root/backup_sql/) 或者本地(../backup_sqls)上传, 传入的需要是.sql文件 :param sql_file: .sql数据库备份文件, 默认就是导出的sql文件名称, 默认文件名称是导出的sql文件 :param database: 恢复的数据库名称,默认是备份数据库(config.yaml中的db_name) """ result = cls.server.execute_cmd(f"ls -l /root/backup_sql/{sql_file}") if "No such file or directory" in result: # 本地上传 cls.server.files_action(1, f"../backup_sqls/{sql_file}", "/root/backup_sql/") cmd = f"docker exec -i {cls.server_settings.get('mysql_container')} mysql -u{cls.settings.get('user')} -p{cls.settings.get('password')} {database} < /root/backup_sql/{sql_file}" cls.server.execute_cmd(cmd) @classmethod def close_client(cls): cls.server.ssh_close()
def handle_path(cls, path_str: str, env: str) -> str: """路径参数处理 :param path_str: 带提取表达式的字符串 /&$.case_005.data.id&/state/&$.case_005.data.create_time& :param env: 环境名称, 对应的是环境基准地址 上述内容表示,从响应字典中提取到case_005字典里data字典里id的值,假设是500,后面&$.case_005.data.create_time& 类似,最终提取结果 return /511/state/1605711095 """ # /&$.case.data.id&/state/&$.case_005.data.create_time& url = ReadFile.read_config(f'$.server.{env}') + rep_expr( path_str, cls.response_dict) allure_step_no(f'请求地址: {url}') return url
class DB: mysql = ReadFile.read_config('$.database') def __init__(self): """ 初始化数据库连接,并指定查询的结果集以字典形式返回 """ self.connection = pymysql.connect( host=self.mysql['host'], port=self.mysql['port'], user=self.mysql['user'], password=self.mysql['password'], db=self.mysql['db_name'], charset=self.mysql.get('charset', 'utf8mb4'), cursorclass=pymysql.cursors.DictCursor) def execute_sql(self, sql: str) -> Union[dict, None]: """ 执行sql语句方法,查询所有结果的sql只会返回一条结果( 比如说: 使用select * from cases , 结果将只会返回第一条数据 {'id': 1, 'name': 'updatehahaha', 'path': None, 'body': None, 'expected': '{"msg": "你好"}', 'api_id': 1, 'create_at': '2021-05-17 17:23:54', 'update_at': '2021-05-17 17:23:54'} ),支持select, delete, insert, update :param sql: sql语句 :return: select 语句 如果有结果则会返回 对应结果字典,delete,insert,update 将返回None """ with self.connection.cursor() as cursor: cursor.execute(sql) result = cursor.fetchone() # 使用commit解决查询数据出现概率查错问题 self.connection.commit() return self.verify(result) def verify(self, result: dict) -> Union[dict, None]: """验证结果能否被json.dumps序列化""" # 尝试变成字符串,解决datetime 无法被json 序列化问题 try: json.dumps(result) except TypeError: # TypeError: Object of type datetime is not JSON serializable for k, v in result.items(): if isinstance(v, datetime): result[k] = str(v) return result def close(self): """关闭数据库连接""" self.connection.close()
# def __init__(self): # """初始化连接Mysql""" # self.connection = pymysql.connect( # host=self.mysql.get('host', 'localhost'), # port=self.mysql.get('port', 3306), # user=self.mysql.get('user', 'root'), # password=self.mysql.get('password', '123456'), # db=self.mysql.get('db_name', 'test'), # charset=self.mysql.get('charset', 'utf8mb4'), # cursorclass=pymysql.cursors.DictCursor # ) def fetch_one(self, sql: str) -> object: """查询数据,查一条""" with self.connection.cursor() as cursor: cursor.execute(sql) result = cursor.fetchone() # 使用commit解决查询数据出现概率查错问题 self.connection.commit() return result def close(self): """关闭数据库连接""" self.connection.close() if __name__ == '__main__': print(ReadFile.read_config('$.database')) DB()
@file: test_api.py @ide: PyCharm @time: 2020/11/22 @desc: 测试方法 """ import os import shutil import pytest from tools import logger from api.base_requests import BaseRequest from tools.data_process import DataProcess from tools.read_file import ReadFile report = ReadFile.read_config('$.file_path.report') logfile = ReadFile.read_config('$.file_path.log') # 读取excel数据对象 cases = ReadFile.read_testcase() class TestApi: @classmethod def run(cls): if os.path.exists('../report'): shutil.rmtree(path='../report') logger.add(logfile, enqueue=True, encoding='utf-8') logger.info('开始测试...') pytest.main(args=[f'--alluredir={report}/data']) os.system(f'allure generate {report}/data -o {report}/html --clean') logger.success('报告已生成')
@pytest.fixture(scope="session") def data_clearing(): """数据清洗""" DataClearing.server_init() # 1. 备份数据库 DataClearing.backup_mysql() yield # 2. 恢复数据库 DataClearing.recovery_mysql() DataClearing.close_client() # 若不需要数据清洗功能,请把get_db()入参拿掉 @pytest.fixture(scope="session") def get_db(data_clearing): """关于其作用域请移步查看官方文档""" try: db = DB() yield db finally: db.close() @pytest.fixture(params=ReadFile.read_testcase()) def cases(request): """用例数据,测试方法参数入参该方法名 cases即可,实现同样的参数化 目前来看相较于@pytest.mark.parametrize 更简洁。 """ return request.param
class DataProcess: response_dict = {} header = ReadFile.read_config('$.request_headers') have_token = header.copy() @classmethod def save_response(cls, key: str, value: object) -> None: """ 保存实际响应 :param key: 保存字典中的key,一般使用用例编号 :param value: 保存字典中的value,使用json响应 """ cls.response_dict[key] = value logger.info(f'添加key: {key}, 对应value: {value}') @classmethod def handle_path(cls, path_str: str) -> str: """路径参数处理 :param path_str: 带提取表达式的字符串 /&$.case_005.data.id&/state/&$.case_005.data.create_time& 上述内容表示,从响应字典中提取到case_005字典里data字典里id的值,假设是500,后面&$.case_005.data.create_time& 类似,最终提取结果 return /511/state/1605711095 """ # /&$.case.data.id&/state/&$.case_005.data.create_time& return rep_expr(path_str, cls.response_dict) @classmethod def handle_header(cls, token: str) -> dict: """处理header :param token: 写: 写入token到header中, 读: 使用带token的header, 空:使用不带token的header return """ if token == '读': return cls.have_token else: return cls.header @classmethod def handler_files(cls, file_obj: str) -> object: """file对象处理方法 :param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"] 实例- 单个文件: &file&D: """ if file_obj == '': return for k, v in convert_json(file_obj).items(): # 多文件上传 if isinstance(v, list): files = [] for path in v: files.append((k, (open(path, 'rb')))) else: # 单文件上传 files = {k: open(v, 'rb')} return files @classmethod def handle_data(cls, variable: str) -> dict: """请求数据处理 :param variable: 请求数据,传入的是可转换字典/json的字符串,其中可以包含变量表达式 return 处理之后的json/dict类型的字典数据 """ if variable == '': return data = rep_expr(variable, cls.response_dict) variable = convert_json(data) return variable @classmethod def handle_sql(cls, sql: str, db: object): """处理sql,并将结果写到响应字典中""" if sql not in ['no', '']: sql = rep_expr(sql, DataProcess.response_dict) else: sql = None allure_step('运行sql', sql) logger.info(sql) if sql is not None: # 查后置sql result = db.fetch_one(sql) allure_step('sql执行结果', {"sql_result": result}) logger.info(f'结果:{result}') if result is not None: # 将查询结果添加到响应字典里面,作用在,接口响应的内容某个字段 直接和数据库某个字段比对,在预期结果中 # 使用同样的语法提取即可 DataProcess.response_dict.update(result) @classmethod def assert_result(cls, response: dict, expect_str: str): """ 预期结果实际结果断言方法 :param response: 实际响应结果 :param expect_str: 预期响应内容,从excel中读取 return None """ # 后置sql变量转换 expect_str = rep_expr(expect_str, DataProcess.response_dict) expect_dict = convert_json(expect_str) index = 0 for k, v in expect_dict.items(): # 获取需要断言的实际结果部分 actual = extractor(response, k) index += 1 logger.info( f'第{index}个断言,实际结果:{actual} | 预期结果:{v} \n断言结果 {actual == v}') allure_step(f'第{index}个断言', f'实际结果:{actual} = 预期结果:{v}') try: assert actual == v except AssertionError: raise AssertionError( f'第{index}个断言失败 -|- 实际结果:{actual} || 预期结果: {v}')
# -*- coding: utf-8 -*- # @Time : 2021/5/2 22:26 # @Author : RanyLra # @Wechat : RanY_Luck # @File : run.py import os import shutil from test.conftest import pytest from tools import logger from tools.read_file import ReadFile from tools.send_email import EmailServe report = ReadFile.read_config('$.file_path.report') logfile = ReadFile.read_config('$.file_path.log') file_path = ReadFile.read_config('$.file_path') s_email = ReadFile.read_config('$.email') def run(): if os.path.exists('report/'): shutil.rmtree(path='report/') logger.add(logfile, enqueue=True, encoding='utf-8') logger.info(""" _ _ _ _____ _ __ _ _ __ (_) / \\ _ _| |_ __|_ _|__ ___| |_ / _` | '_ \\| | / _ \\| | | | __/ _ \\| |/ _ \\/ __| __| | (_| | |_) | |/ ___ \\ |_| | || (_) | | __/\\__ \\ |_ \\__,_| .__/|_/_/ \\_\\__,_|\\__\\___/|_|\\___||___/\\__| |_| Starting ... ... ... """)
""" @project: apiTest @author: Tamia @file: run.py @ide: PyCharm @time: 2020/12/16 @desc: 运行文件 """ import os import shutil from test.conftest import pytest from tools import logger from tools.read_file import ReadFile report = ReadFile.read_config('$.file_path.report') logfile = ReadFile.read_config('$.file_path.log') def run(): if os.path.exists('report/'): shutil.rmtree(path='report/') logger.add(logfile, enqueue=True, encoding='utf-8') logger.info(""" _ _ _ _____ _ __ _ _ __ (_) / \ _ _| |_ __|_ _|__ ___| |_ / _` | '_ \| | / _ \| | | | __/ _ \| |/ _ \/ __| __| | (_| | |_) | |/ ___ \ |_| | || (_) | | __/\__ \ |_ \__,_| .__/|_/_/ \_\__,_|\__\___/|_|\___||___/\__| |_| Starting ... ... ...
class DataProcess: response_dict = {} header = ReadFile.read_config('$.request_headers') @classmethod def save_response(cls, key: str, value: object) -> None: """ 保存实际响应 :param key: 保存字典中的key,一般使用用例编号 :param value: 保存字典中的value,使用json响应 """ cls.response_dict[key] = value logger.info(f'添加key: {key}, 对应value: {value}') allure_step('存储实际响应', cls.response_dict) @classmethod def handle_path(cls, path_str: str, env: str) -> str: """路径参数处理 :param path_str: 带提取表达式的字符串 /&$.case_005.data.id&/state/&$.case_005.data.create_time& :param env: 环境名称, 对应的是环境基准地址 上述内容表示,从响应字典中提取到case_005字典里data字典里id的值,假设是500,后面&$.case_005.data.create_time& 类似,最终提取结果 return /511/state/1605711095 """ # /&$.case.data.id&/state/&$.case_005.data.create_time& url = ReadFile.read_config(f'$.server.{env}') + rep_expr( path_str, cls.response_dict) allure_step_no(f'请求地址: {url}') return url @classmethod def handle_header(cls, header_str: str) -> dict: """处理header, 将用例中的表达式处理后 追加到基础header中 :header_str: 用例栏中的header return header: """ if header_str == '': header_str = '{}' cls.header.update(cls.handle_data(header_str)) allure_step('请求头', cls.header) return cls.header @classmethod def handler_files(cls, file_obj: str) -> object: """file对象处理方法 :param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"] 实例- 单个文件: &file&D: """ if file_obj != '': for k, v in convert_json(file_obj).items(): # 多文件上传 if isinstance(v, list): files = [] for path in v: files.append((k, (open(path, 'rb')))) else: # 单文件上传 files = {k: open(v, 'rb')} allure_step('上传文件', file_obj) return files @classmethod def handle_data(cls, variable: str) -> dict: """请求数据处理 :param variable: 请求数据,传入的是可转换字典/json的字符串,其中可以包含变量表达式 return 处理之后的json/dict类型的字典数据 """ if variable != '': data = rep_expr(variable, cls.response_dict) variable = convert_json(data) return variable @classmethod def handle_sql(cls, sql: str, db: DB): """ 处理sql,如果sql执行的结果不会空,执行sql的结果和响应结果字典合并 :param sql: 支持单条或者多条sql,其中多条sql使用 ; 进行分割 多条sql,在用例中填写方式如下select * from user; select * from goods 每条sql语句之间需要使用 ; 来分割 单条sql,select * from user 或者 select * from user; :param db: 数据库连接对象 :return: """ sql = rep_expr(sql, DataProcess.response_dict) for sql in sql.split(";"): sql = sql.strip() if sql == '': continue # 查后置sql result = db.execute_sql(sql) allure_step(f'执行sql: {sql}', result) logger.info(f'执行sql: {sql} \n 结果: {result}') if result is not None: # 将查询结果添加到响应字典里面,作用在,接口响应的内容某个字段 直接和数据库某个字段比对,在预期结果中 # 使用同样的语法提取即可 DataProcess.response_dict.update(result) @classmethod def assert_result(cls, response: dict, expect_str: str): """ 预期结果实际结果断言方法 :param response: 实际响应结果 :param expect_str: 预期响应内容,从excel中读取 return None """ # 后置sql变量转换 expect_str = rep_expr(expect_str, DataProcess.response_dict) expect_dict = convert_json(expect_str) index = 0 for k, v in expect_dict.items(): # 获取需要断言的实际结果部分 actual = extractor(response, k) index += 1 logger.info( f'第{index}个断言,实际结果:{actual} | 预期结果:{v} \n断言结果 {actual == v}') allure_step(f'第{index}个断言', f'实际结果:{actual} = 预期结果:{v}') try: assert actual == v except AssertionError: raise AssertionError( f'第{index}个断言失败 -|- 实际结果:{actual} || 预期结果: {v}')
class DataProcess: response_dict = {} header = ReadFile.read_config('$.request_headers') @classmethod def save_response(cls, key: str, value: object) -> None: """ 保存实际响应 :param key: 保存字典中key,一般使用用例编号 :param value: 保存字典中的value,使用json献映 :return: 返回最终保存结果 """ cls.response_dict[key] = value logger.info(f'添加key:{key},对应value:{value}') @classmethod def handle_path(cls, path_str: str) -> str: """ 路径参数处理 :param path_str: 带提取表达式的字符串 /&$.case_005_data.id&/stats/&$.case_005.data.create_time& 上述内容解析为:从响应字典中提取到case_005字典里datazi字典里的id的值,假设是500,后面&$.case_005.data.create_time& 类似,最终提取结果为: :return: /500/state/123456 """ # /&$.case_005.data.id&/state/&$.case_005.data.create_time& return rep_expr(path_str, cls.response_dict) @classmethod def handle_header(cls, header_str: str) -> dict: """ 处理header,将用例中的表达式处理后,追加到基础header中 :param header_str: 用例栏中的header :return: 返回header """ if header_str == '': header_str = '{}' cls.header.update(cls.handle_data(header_str)) return cls.header @classmethod def handler_files(cls, file_obj: str) -> object: """ file对象处理方法 :param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1","文件地址2"] :return: {"files":["C:\\Users\\ranyong\\Desktop\\b.jpg", "C:\\Users\\ranyong\\Desktop\\c.jpg"]} """ if file_obj == '': return for k, v in convert_json(file_obj).items(): # 多文件上传 if isinstance(v, list): files = [] for path in v: files.append((k, (open(path, 'rb')))) else: # 单文件上传 files = {k: open(v, 'rb')} return files @classmethod def handle_data(cls, variable: str) -> dict: """ 请求数据处理 :param variable: 请求数据,传入的是可换成字典/json的字符串,其中可以包含变量表达式 :return: 处理之后的json/dict类型的字典数据 """ if variable == '': return data = rep_expr(variable, cls.response_dict) variable = convert_json(data) return variable @classmethod def handle_sql(cls, sql: str, db: object): """ 处理sql,并将结果写在响应字典中 :param sql: sql语句 :param db: 数据库名 :return: 写到响应字典中 """ if sql not in ['no', '']: sql = rep_expr(sql, DataProcess.response_dict) else: sql = None allure_step('运行sql', sql) logger.info(sql) if sql is not None: # 查后置sql result = db.fetch_one(sql) allure_step('sql执行结果', result) logger.info(f'结果:{result}') if result is not None: # 将查询结果添加到响应字典里面,作用在接口响应的内容某个字段,直接和数据库某个字段比对,在预期结果中 # 使用同样的语法提取即可 DataProcess.response_dict.update(result) @classmethod def assert_result(cls, response: dict, expect_str: str): """ 预期结果实际结果断言方式 :param response: 实际响应结果 :param expect_str: 预期响应内容,从excel中读取 :return: None """ # 后置sql变量控制 expect_str = rep_expr(expect_str, DataProcess.response_dict) expect_dict = convert_json(expect_str) index = 0 for k, v in expect_dict.items(): # 获取需要断言的实际结果部分 actual = extractor(response, k) index += 1 logger.info(f'第{index}个断言', f'实际结果:{actual} || 预期结果:{v} \n断言结果 {actual == v}') allure_step(f'第{index}个断言', f'实际结果:{actual}=预期结果:{v}') try: assert actual == v except AssertionError: raise AssertionError( f'第{index}个断言失败 -|- 实际结果:{actual} || 你的预期结果: {v}')
@file: run.py @ide: PyCharm @time: 2020/12/16 @github: https://github.com/zy7y @site: https://cnblogs.com/zy7y @desc: 运行文件 """ import os import shutil from test.conftest import pytest from tools import logger from tools.read_file import ReadFile from tools.send_email import EmailServe file_path = ReadFile.read_config('$.file_path') email = ReadFile.read_config('$.email') def run(): if os.path.exists('report/'): shutil.rmtree(path='report/') logger.add(file_path['log'], enqueue=True, encoding='utf-8') logger.info(""" _ _ _ _____ _ __ _ _ __ (_) / \\ _ _| |_ __|_ _|__ ___| |_ / _` | '_ \\| | / _ \\| | | | __/ _ \\| |/ _ \\/ __| __| | (_| | |_) | |/ ___ \\ |_| | || (_) | | __/\\__ \\ |_ \\__,_| .__/|_/_/ \\_\\__,_|\\__\\___/|_|\\___||___/\\__| |_| Starting ... ... ...
class DataProcess: response_dict = {} header = ReadFile.read_config('$.request_headers') have_token = header.copy() @classmethod def save_response(cls, key: str, value: object) -> None: """ 保存实际响应 :param key: 保存字典中的key,一般使用用例编号 :param value: 保存字典中的value,使用json响应 """ cls.response_dict[key] = value logger.info(f'添加key: {key}, 对应value: {value}') @classmethod def handle_path(cls, path_str: str) -> str: """路径参数处理 :param path_str: 带提取表达式的字符串 /&$.case_005.data.id&/state/&$.case_005.data.create_time& 上述内容表示,从响应字典中提取到case_005字典里data字典里id的值,假设是500,后面&$.case_005.data.create_time& 类似,最终提取结果 return /511/state/1605711095 """ # /&$.case.data.id&/state/&$.case_005.data.create_time& return rep_expr(path_str, cls.response_dict) @classmethod def handle_header(cls, token: str) -> dict: """处理header :param token: 写: 写入token到header中, 读: 使用带token的header, 空:使用不带token的header return """ if token == '读': return cls.have_token else: return cls.header @classmethod def handler_files(cls, file_obj: str) -> object: """file对象处理方法 :param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"] 实例- 单个文件: &file&D: """ if file_obj == '': return for k, v in convert_json(file_obj).items(): # 多文件上传 if isinstance(v, list): files = [] for path in v: files.append((k, (open(path, 'rb')))) else: # 单文件上传 files = {k: open(v, 'rb')} return files @classmethod def handle_data(cls, variable: str) -> dict: """请求数据处理 :param variable: 请求数据,传入的是可转换字典/json的字符串,其中可以包含变量表达式 return 处理之后的json/dict类型的字典数据 """ if variable == '': return data = rep_expr(variable, cls.response_dict) variable = convert_json(data) return variable @classmethod def assert_result(cls, response: dict, expect_str: str): """ 预期结果实际结果断言方法 :param response: 实际响应字典 :param expect_str: 预期响应内容,从excel中读取 return None """ expect_dict = convert_json(expect_str) index = 0 for k, v in expect_dict.items(): actual = extractor(response, k) index += 1 logger.info( f'第{index}个断言,实际结果:{actual} | 预期结果:{v} \n断言结果 {actual == v}') allure_step(f'第{index}个断言', f'实际结果:{actual} = 预期结果:{v}') assert actual == v