请求装饰器处理发送请求

This commit is contained in:
chenyongzhiaaron 2023-08-14 17:55:28 +08:00
parent 76df35c96e
commit 9eea2cc951
31 changed files with 254 additions and 111 deletions

View File

@ -4,17 +4,7 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="eded71e8-6551-463d-9bd4-cdbb3ffc536c" name="更改" comment="增加依赖装饰器,增加自动安装依赖文件,修改报告日志输出边距及颜色">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/OutPut/reports/report.html" beforeDir="false" afterPath="$PROJECT_DIR$/OutPut/reports/report.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cases/cases/test_cases.xlsx" beforeDir="false" afterPath="$PROJECT_DIR$/cases/cases/test_cases.xlsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/core/testRunner.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/core/testRunner.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/utils/decorators.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/decorators.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/utils/install_dependencies.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/output/reports/history.json" beforeDir="false" afterPath="$PROJECT_DIR$/output/reports/history.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/dingtalk.md" beforeDir="false" afterPath="$PROJECT_DIR$/templates/dingtalk.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/weChat.md" beforeDir="false" afterPath="$PROJECT_DIR$/templates/weChat.md" afterDir="false" />
</list>
<list default="true" id="eded71e8-6551-463d-9bd4-cdbb3ffc536c" name="更改" comment="优化钉钉报告样式" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -194,8 +184,8 @@
<recent_temporary>
<list>
<item itemvalue="Python.run" />
<item itemvalue="Python.run_script_main" />
<item itemvalue="Python.comparators" />
<item itemvalue="Python.run_script_main" />
<item itemvalue="Python 测试.Python 测试 (test_executor.py 内)" />
<item itemvalue="Python.dingding" />
</list>
@ -235,7 +225,8 @@
<workItem from="1691638175448" duration="3625000" />
<workItem from="1691657355432" duration="5387000" />
<workItem from="1691667714661" duration="8686000" />
<workItem from="1691973725101" duration="6000000" />
<workItem from="1691973725101" duration="6601000" />
<workItem from="1691980768817" duration="14005000" />
</task>
<task id="LOCAL-00001" summary="优化代码">
<option name="closed" value="true" />
@ -373,7 +364,15 @@
<option name="project" value="LOCAL" />
<updated>1691722760750</updated>
</task>
<option name="localTasksCounter" value="18" />
<task id="LOCAL-00018" summary="优化钉钉报告样式">
<option name="closed" value="true" />
<created>1691980245927</created>
<option name="number" value="00018" />
<option name="presentableId" value="LOCAL-00018" />
<option name="project" value="LOCAL" />
<updated>1691980245927</updated>
</task>
<option name="localTasksCounter" value="19" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -393,7 +392,8 @@
<MESSAGE value="更新说明文档" />
<MESSAGE value="更友好的异常信息输出" />
<MESSAGE value="增加依赖装饰器,增加自动安装依赖文件,修改报告日志输出边距及颜色" />
<option name="LAST_COMMIT_MESSAGE" value="增加依赖装饰器,增加自动安装依赖文件,修改报告日志输出边距及颜色" />
<MESSAGE value="优化钉钉报告样式" />
<option name="LAST_COMMIT_MESSAGE" value="优化钉钉报告样式" />
</component>
<component name="com.github.evgenys91.machinet.common.dslhistory.DslHistoryState">
<option name="historyDtoById">
@ -414,7 +414,7 @@
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/apitest$load_modules_from_folder.coverage" NAME="load_modules_from_folder 覆盖结果" MODIFIED="1691398471049" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/validation" />
<SUITE FILE_PATH="coverage/apitest$run.coverage" NAME="run 覆盖结果" MODIFIED="1691979676787" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/apitest$run.coverage" NAME="run 覆盖结果" MODIFIED="1692004486581" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/apitest$Unittest__test_api_py__.coverage" NAME="Unittest (test_api.py 内) 覆盖结果" MODIFIED="1689907531802" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test_script" />
<SUITE FILE_PATH="coverage/apitest$dingding.coverage" NAME="dingding 覆盖结果" MODIFIED="1691483158998" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/notiction" />
<SUITE FILE_PATH="coverage/apitest$assert_dict.coverage" NAME="assert_dict 覆盖结果" MODIFIED="1691034548959" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/data_extraction" />
@ -436,7 +436,7 @@
<SUITE FILE_PATH="coverage/apitest$resultPush.coverage" NAME="resultPush 覆盖结果" MODIFIED="1691465454364" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/unittestreportnew/core" />
<SUITE FILE_PATH="coverage/apitest$.coverage" NAME=" 覆盖结果" MODIFIED="1691568903890" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test_script" />
<SUITE FILE_PATH="coverage/apitest$method_chain.coverage" NAME="method_chain 覆盖结果" MODIFIED="1690967090148" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/debug" />
<SUITE FILE_PATH="coverage/apitest$comparators.coverage" NAME="comparators 覆盖结果" MODIFIED="1691666161767" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/validation" />
<SUITE FILE_PATH="coverage/apitest$comparators.coverage" NAME="comparators 覆盖结果" MODIFIED="1691996672342" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/validation" />
<SUITE FILE_PATH="coverage/apitest$encryption_str.coverage" NAME="encryption_str 覆盖结果" MODIFIED="1690796164466" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/crypto" />
<SUITE FILE_PATH="coverage/apitest$data_extractor.coverage" NAME="data_extractor 覆盖结果" MODIFIED="1691398486016" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/data_extraction" />
<SUITE FILE_PATH="coverage/apitest$get_set.coverage" NAME="get_set 覆盖结果" MODIFIED="1689930576115" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/debug" />

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -83,6 +83,8 @@ class HttpClient(LoadModulesFromFolder):
[i.close() for i in fs if len(fs) > 0]
try:
self.response_json = self.response.json()
self.update_environments("responseStatusCode", self.response.status_code)
self.update_environments("responseTime", round(self.response.elapsed.total_seconds() * 1000, 2))
except Exception as e:
ResponseJsonConversionError(self.response.text, str(e))
self.response_json = None

View File

@ -13,7 +13,7 @@ import time
from common import bif_functions
from common.crypto.encrypt_data import EncryptData
from common.database.mysql_client import MysqlClient
from common.utils.decorators import singleton
from common.utils.decorators import singleton, send_request_decorator
from common.utils.exceptions import *
from common.validation.extractor import Extractor
from common.validation.load_and_execute_script import LoadScript
@ -46,13 +46,18 @@ class Action(Extractor, LoadScript, Validator):
return self.variables
@property
def variables(self, key=None):
return self.__variables if not key else self.__variables.get(key)
@send_request_decorator
def send_request(self, host, method, extract_request_data):
@variables.setter
def variables(self, item):
self.__variables = item
url, kwargs = self.prepare_request(extract_request_data, self.variables)
self.http_client(host, url, method, **kwargs)
def prepare_request(self, extract_request_data, item):
item = self.replace_dependent_parameter(item)
url, query_str, request_data, headers, request_data_type, h_crypto, r_crypto = self.request_info(item)
headers, request_data = self.analysis_request(request_data, h_crypto, headers, r_crypto, extract_request_data)
kwargs = {request_data_type: request_data, "headers": headers, "params": query_str}
return url, kwargs
def analysis_request(self, request_data, headers_crypto, headers, request_crypto, extract_request_data):
headers, request_data = self.encrypt.encrypts(headers_crypto, headers, request_crypto, request_data)
@ -60,39 +65,6 @@ class Action(Extractor, LoadScript, Validator):
self.substitute_data(request_data, jp_dict=extract_request_data)
return headers, request_data
def send_request(self, host, url, method, teardown_script, **kwargs):
self.http_client(host, url, method, **kwargs)
self.update_environments("responseStatusCode", self.response.status_code)
self.update_environments("responseTime", round(self.response.elapsed.total_seconds() * 1000, 2))
self.execute_dynamic_code(self.response, teardown_script)
def analysis_response(self, sheet, iid, name, desc, regex, keys, deps, jp_dict):
try:
self.substitute_data(self.response_json, regex=regex, keys=keys, deps=deps, jp_dict=jp_dict)
except Exception as err:
msg = f"| 分析响应失败:{sheet}_{iid}_{name}_{desc}"
f"\nregex={regex};"
f" \nkeys={keys};"
f"\ndeps={deps};"
f"\njp_dict={jp_dict}"
f"\n{err}"
ParameterExtractionError(msg, err)
def execute_validation(self, excel, sheet, iid, name, desc, expected):
try:
self.run_validate(expected, self.response_json)
result = "PASS"
except Exception as e:
result = "FAIL"
error_info = f"| exception case:**{sheet}_{iid}_{name}_{desc},{self.assertions}"
AssertionFailedError(error_info, e)
raise e
finally:
print(f'| <span style="color:yellow">断言结果-->{self.assertions}</span>\n')
print("-" * 50)
response = self.response.text if self.response is not None else str(self.response)
excel.write_back(sheet_name=sheet, i=iid, response=response, result=result, assertions=str(self.assertions))
@staticmethod
def base_info(item):
"""
@ -104,11 +76,9 @@ class Action(Extractor, LoadScript, Validator):
sleep_time = item.pop("Time")
name = item.pop("Name")
desc = item.pop("Description")
headers_crypto = item.pop("HeadersCrypto")
request_data_crypto = item.pop("RequestDataCrypto")
method = item.pop("Method")
expected = item.pop("Expected")
return sheet, item_id, condition, sleep_time, name, desc, headers_crypto, request_data_crypto, method, expected
return sheet, item_id, condition, sleep_time, name, desc, method, expected
@staticmethod
def sql_info(item):
@ -143,8 +113,10 @@ class Action(Extractor, LoadScript, Validator):
request_data = item.pop("RequestData")
headers = item.pop("Headers")
request_data_type = item.pop("RequestDataType") if item.get("RequestDataType") else 'params'
headers_crypto = item.pop("HeadersCrypto")
request_data_crypto = item.pop("RequestDataCrypto")
return url, query_str, request_data, headers, request_data_type
return url, query_str, request_data, headers, request_data_type, headers_crypto, request_data_crypto
@staticmethod
def script(item):
@ -168,6 +140,7 @@ class Action(Extractor, LoadScript, Validator):
def exc_sql(self, item):
sql, sql_params_dict = self.sql_info(item)
self.variables = item
sql = self.replace_dependent_parameter(sql)
if sql:
client = MysqlClient(self.db_config)
@ -176,6 +149,42 @@ class Action(Extractor, LoadScript, Validator):
if execute_sql_results and sql_params_dict:
self.substitute_data(execute_sql_results, jp_dict=sql_params_dict)
def analysis_response(self, sheet, iid, name, desc, regex, keys, deps, jp_dict):
try:
self.substitute_data(self.response_json, regex=regex, keys=keys, deps=deps, jp_dict=jp_dict)
except Exception as err:
msg = f"| 分析响应失败:{sheet}_{iid}_{name}_{desc}"
f"\nregex={regex};"
f" \nkeys={keys};"
f"\ndeps={deps};"
f"\njp_dict={jp_dict}"
f"\n{err}"
ParameterExtractionError(msg, err)
def execute_validation(self, excel, sheet, iid, name, desc, expected):
expected = self.replace_dependent_parameter(expected)
try:
self.run_validate(expected, self.response_json)
result = "PASS"
except Exception as e:
result = "FAIL"
error_info = f"| exception case:**{sheet}_{iid}_{name}_{desc},{self.assertions}"
AssertionFailedError(error_info, e)
raise e
finally:
print(f'| <span style="color:yellow">断言结果-->{self.assertions}</span>\n')
print("-" * 50 + "我是分割线" + "-" * 50)
response = self.response.text if self.response is not None else str(self.response)
excel.write_back(sheet_name=sheet, i=iid, response=response, result=result, assertions=str(self.assertions))
@property
def variables(self, key=None):
return self.__variables if not key else self.__variables.get(key)
@variables.setter
def variables(self, item):
self.__variables = item
if __name__ == '__main__':
print(Action())

View File

@ -20,7 +20,7 @@ from common.utils.exceptions import RequestSendingError
def singleton(cls):
"""
Args:
cls:被装饰类
cls:Decorated class
Returns:
"""
instance = {}
@ -35,7 +35,7 @@ def singleton(cls):
def request_retry_on_exception(retries=2, delay=1.5):
"""失败请求重发"""
"""Retry on Failed Requests"""
def request_decorator(func):
e = None
@ -54,7 +54,6 @@ def request_retry_on_exception(retries=2, delay=1.5):
print(f"| 接口状态--> {response.status_code}")
print(f"| 接口耗时--> {response.elapsed}")
print(f"| 接口响应--> {response.text}")
except Exception as error:
e = error
@ -70,7 +69,7 @@ def request_retry_on_exception(retries=2, delay=1.5):
def list_data(datas):
"""
:param datas: 测试数据
:param datas: Test data
:return:
"""
@ -83,7 +82,7 @@ def list_data(datas):
def yaml_data(file_path):
"""
:param file_path: yaml文件路径
:param file_path:YAML file path
:return:
"""
@ -102,7 +101,7 @@ def yaml_data(file_path):
def json_data(file_path):
"""
:param file_path: json文件路径
:param file_path: YAML file path
:return:
"""
@ -123,7 +122,7 @@ import time
def run_count(count, interval, func, *args, **kwargs):
"""运行计数"""
"""Run Count"""
for i in range(count):
try:
func(*args, **kwargs)
@ -141,9 +140,9 @@ def run_count(count, interval, func, *args, **kwargs):
def rerun(count, interval=2):
"""
单个测试用例重运行的装饰器,注意点如果使用了ddt,那么该方法要在用在ddt之前
:param count: 失败重运行次数
:param interval: 每次重运行间隔时间,默认三秒钟
Decorator for rerunning a single test case; note that if using ddt, this method should be used before ddt
:param count: Number of retries on failure
:param interval: Interval time between each retry, default is three seconds
:return:
"""
@ -157,13 +156,15 @@ def rerun(count, interval=2):
def install_dependencies(func):
"""Checking and Installing Dependencies"""
@wraps(func)
def wrapper(*args, **kwargs):
try:
print("---------------- 檢測并且安装依赖文件 ----------------")
print("---------------- Checking and Installing Dependencies ----------------")
subprocess.check_call(["pipenv", "install"])
print("---------------- 成功安装所有依赖文件 ----------------")
print("---------------- Successfully Installed All Dependencies ----------------")
except Exception as e:
print(f"Failed to install dependencies: {str(e)}")
@ -172,3 +173,27 @@ def install_dependencies(func):
return func(*args, **kwargs)
return wrapper
def send_request_decorator(func):
"""Decorator to handle the logic of sending requests."""
def decorator(self, host, method, extract_request_data):
"""Handles setup, request execution, and teardown logic for sending requests.
Args:
self: The instance of the class.
host: The host for the request.
method: The HTTP method for the request.
extract_request_data: Data for extracting request details.
Returns:
The response from the request.
"""
setup_script, teardown_script = self.script(self.variables)
self.execute_dynamic_code(self.variables, setup_script)
response = func(self, host, method, extract_request_data)
self.execute_dynamic_code(self.response, teardown_script)
return response
return decorator

View File

@ -175,10 +175,8 @@ def length_lte(actual_value, expect_value):
Returns:
"""
assert isinstance(expect_value, (
int,)), p_string(actual_value, expect_value)
assert len(
actual_value) <= expect_value, p_string(actual_value, expect_value)
assert isinstance(expect_value, (int,)), p_string(actual_value, expect_value)
assert len(actual_value) <= expect_value, p_string(actual_value, expect_value)
def contains(actual_value, expect_value):
@ -296,3 +294,4 @@ def endswith(actual_value, expect_value):
if __name__ == '__main__':
eq(1, "1")
lte(1.23, 400.3)

View File

@ -74,7 +74,7 @@ class Validator(Loaders):
check_item = validate_variable['check']
expect_value = validate_variable['expect']
comparator = validate_variable['comparator']
if not check_item.startswith("$"):
if not str(check_item).startswith("$"):
actual_value = check_item
else:
actual_value = Extractor.extract_value_by_jsonpath(resp_obj=resp, expr=check_item)
@ -111,7 +111,7 @@ class Validator(Loaders):
self.uniform_validate(validate_variables)
if not self.validate_variables_list:
raise InvalidParameterFormatError(self.validate_variables_list,
"uniform_validate 执行失败,无法进行 validate 校验")
"uniform_validate 执行失败,无法进行 validate 校验")
self.validate(resp)

File diff suppressed because one or more lines are too long

View File

@ -36,27 +36,19 @@ class TestProjectApi(unittest.TestCase):
@list_data(test_case)
def test_api(self, item):
sheet, iid, condition, st, name, desc, h_crypto, r_crypto, method, expected = self.action.base_info(item)
sheet, iid, condition, st, name, desc, method, expected = self.action.base_info(item)
if self.action.is_run(condition):
self.skipTest("这个测试用例听说泡面比较好吃,所以放弃执行了!!")
regex, keys, deps, jp_dict, ex_request_data = self.action.extractor_info(item)
setup_script, teardown_script = self.action.script(item)
self.action.pause_execution(st)
self.action.exc_sql(item)
if method.upper() == 'SQL':
self.skipTest("这条测试用例被 SQL 吃了,所以放弃执行了!!")
item = self.action.execute_dynamic_code(item, setup_script)
# prepost_script = f"prepost_script_{sheet}_{iid}.py"
# item = self.action.load_and_execute_script(Config.SCRIPTS_DIR, prepost_script, "setup", item)
item = self.action.replace_dependent_parameter(item)
url, query_str, request_data, headers, request_data_type = self.action.request_info(item)
headers, request_data = self.action.analysis_request(request_data, h_crypto, headers, r_crypto, ex_request_data)
kwargs = {request_data_type: request_data, 'headers': headers, "params": query_str}
self.action.send_request(host, url, method, teardown_script, **kwargs)
self.action.send_request(host, method, ex_request_data)
self.action.analysis_response(sheet, iid, name, desc, regex, keys, deps, jp_dict)
expected = self.action.replace_dependent_parameter(expected)
self.action.execute_validation(excel, sheet, iid, name, desc, expected)
@classmethod