兼容断言检查项不使用jsonpath提取参数

This commit is contained in:
chenyongzhiaaron 2023-08-03 11:59:07 +08:00
parent 737624746f
commit ffe6fb2553
41 changed files with 771 additions and 759 deletions

3
.gitignore vendored
View File

@ -15,6 +15,9 @@ OutPut/log
*.ipa
*.apk
*.log
*.xhtml
*.xml
.idea
__pycache__
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important

View File

@ -4,10 +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$/common/data_extraction/dependent_parameter.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/data_extraction/dependent_parameter.py" 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" />
@ -69,7 +66,29 @@
<recent name="D:\app\apitest\common\parsing" />
</key>
</component>
<component name="RunManager" selected="Python.dependent_parameter">
<component name="RunManager" selected="Python 测试.Python 测试 (test_executor.py 内)">
<configuration name="assert_dict" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="api_project" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/common/data_extraction" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/common/data_extraction/assert_dict.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="dependent_parameter" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="api_project" />
<option name="INTERPRETER_OPTIONS" value="" />
@ -92,7 +111,7 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="load_modules_from_folder" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<configuration name="extractor" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="api_project" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
@ -105,7 +124,7 @@
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/common/validation/load_modules_from_folder.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/common/validation/extractor.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
@ -114,7 +133,7 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="loaders" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<configuration name="validator" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="api_project" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
@ -127,7 +146,7 @@
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/common/validation/loaders.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/common/validation/validator.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
@ -136,57 +155,28 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="method_chain" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<configuration name="Python 测试 (test_executor.py 内)" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
<module name="api_project" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/debug" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test_script" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/debug/method_chain.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="re_chain" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="api_project" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/debug" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/debug/re_chain.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;$PROJECT_DIR$/test_script/test_executor.py&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python 测试.Python 测试 (test_executor.py 内)" />
<item itemvalue="Python.assert_dict" />
<item itemvalue="Python.validator" />
<item itemvalue="Python.extractor" />
<item itemvalue="Python.dependent_parameter" />
<item itemvalue="Python.method_chain" />
<item itemvalue="Python.re_chain" />
<item itemvalue="Python.loaders" />
<item itemvalue="Python.load_modules_from_folder" />
</list>
</recent_temporary>
</component>
@ -218,7 +208,8 @@
<workItem from="1690855086910" duration="749000" />
<workItem from="1690855882311" duration="1981000" />
<workItem from="1690873614157" duration="4828000" />
<workItem from="1690937027137" duration="26771000" />
<workItem from="1690937027137" duration="37912000" />
<workItem from="1691034024300" duration="1062000" />
</task>
<task id="LOCAL-00001" summary="优化代码">
<option name="closed" value="true" />
@ -316,7 +307,15 @@
<option name="project" value="LOCAL" />
<updated>1690969473478</updated>
</task>
<option name="localTasksCounter" value="13" />
<task id="LOCAL-00013" summary="增加处理函数调用链变量以及修复动态函数中传参失效的问题">
<option name="closed" value="true" />
<created>1690970941519</created>
<option name="number" value="00013" />
<option name="presentableId" value="LOCAL-00013" />
<option name="project" value="LOCAL" />
<updated>1690970941519</updated>
</task>
<option name="localTasksCounter" value="14" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -334,6 +333,17 @@
<MESSAGE value="增加处理函数调用链变量以及修复动态函数中传参失效的问题" />
<option name="LAST_COMMIT_MESSAGE" value="增加处理函数调用链变量以及修复动态函数中传参失效的问题" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/common/validation/extractor.py</url>
<line>10</line>
<option name="timeStamp" value="12" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
<component name="com.github.evgenys91.machinet.common.dslhistory.DslHistoryState">
<option name="historyDtoById">
<map>
@ -356,10 +366,15 @@
<SUITE FILE_PATH="coverage/apitest$Unittest__test_executor_py__.coverage" NAME="Unittest (test_executor.py 内) 覆盖结果" MODIFIED="1690850721335" 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$http_client.coverage" NAME="http_client 覆盖结果" MODIFIED="1690850772526" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common/http_client" />
<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$dependent_parameter.coverage" NAME="dependent_parameter 覆盖结果" MODIFIED="1690970103268" 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$extractor.coverage" NAME="extractor 覆盖结果" MODIFIED="1691031544934" 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$dependent_parameter.coverage" NAME="dependent_parameter 覆盖结果" MODIFIED="1691028535712" 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$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" />
<SUITE FILE_PATH="coverage/apitest$re_chain.coverage" NAME="re_chain 覆盖结果" MODIFIED="1690962595251" 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$validator.coverage" NAME="validator 覆盖结果" MODIFIED="1691032283545" 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$loaders.coverage" NAME="loaders 覆盖结果" MODIFIED="1690942567666" 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$.coverage" NAME=" 覆盖结果" MODIFIED="1691035043685" 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$requestRecord__1_.coverage" NAME="requestRecord (1) 覆盖结果" MODIFIED="1690531988570" 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$__init__.coverage" NAME="__init__ 覆盖结果" MODIFIED="1691028135541" 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$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$action.coverage" NAME="action 覆盖结果" MODIFIED="1689907783681" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/common" />
<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" />

Binary file not shown.

View File

@ -16,135 +16,133 @@ from common.utils.environments import Environments
from common.data_extraction import logger
REPLACE_DICT = {
"null": None,
"True": True,
"false": False
"null": None,
"True": True,
"false": False
}
class DataExtractor(Environments):
def __init__(self):
super().__init__()
@logger.log_decorator("提取参数出现了意想不到的错误!!")
def substitute_data(self, response, regex=None, keys=None, deps=None, jp_dict=None):
"""
方法接收一个正则表达式 regex 和一个关联参数表 deps用于从接口返回的数据中提取关联参数
它会从接口返回的数据中使用正则表达式 regex 和正则表达式返回结果的键列表 keys 提取数据并将其更新到关联参数表中
然后它会使用 subs_deps subs_lists 方法提取更多的关联参数最后它将更新后的关联参数表设置为 Environments 类的静态变量并将其返回
Args:
response: 被提取数据对象
regex: 正则表达式 r'"id": (\d+), "name": "(\w+)",'
keys: 接收正则表达式返回结果的key ["a", "b"]
deps: "name=data[0].name;ok=data[0].id;an=data[0].age[3].a"
jp_dict: jsonpath 提取方式入参{"k": "$.data", "x": "$.data[0].age[3].a"}
Returns:
"""
response = response
if not isinstance(response, (dict, str, list)):
logger.error(f"被提取对象非字典、非字符串、非列表不执行jsonpath提取被提取对象: {response}")
return {}
if regex and keys:
self.substitute_regex(response, regex, keys)
response = response if isinstance(response, (dict, list)) else json.loads(response)
if deps:
self.substitute_route(response, deps)
if jp_dict:
self.substitute_jsonpath(response, jp_dict)
def substitute_regex(self, response, regex, keys):
"""
方法用于使用正则表达式 regex 和正则表达式返回结果的键列表 keys 从接口返回的数据中提取数据并将其更新到关联参数表中
Args:
response:
regex: 正则表达式r'"id": (\d+), "name": "(\w+)",'
keys:结果键列表["a", "b"],
Returns:
"""
response = json.dumps(response) if isinstance(response, (dict, list)) else response
match = re.search(regex, response)
if not match:
return {}
groups = match.groups()
for i, key in enumerate(keys):
try:
self.update_environments(key, groups[i])
except:
self.update_environments(key, None)
def substitute_route(self, response, route_str):
"""
想字典一样取值:name=data[0].name;ok=data[0].id;an=data[0].age
Args:
response:
route_str:
def __init__(self):
super().__init__()
Returns:
@logger.log_decorator("提取参数出现了意想不到的错误!!")
def substitute_data(self, response, regex=None, keys=None, deps=None, jp_dict=None):
"""
数据提取
Args:
response: 被提取数据对象
regex: 正则表达式 r'"id": (\d+), "name": "(\w+)",'
keys: 接收正则表达式返回结果的key ["a", "b"]
deps: "name=data[0].name;ok=data[0].id;an=data[0].age[3].a"
jp_dict: jsonpath 提取方式入参{"k": "$.data", "x": "$.data[0].age[3].a"}
Returns:
"""
"""
deps_list = re.sub(f"[\r\n]+", "", route_str).split(";")
for dep_item in deps_list:
key, value_path = dep_item.split("=")
value_path_parts = re.findall(r'\w+', value_path)
temp = response
for part in value_path_parts:
if isinstance(temp, dict):
temp = temp.get(part)
elif isinstance(temp, list):
if part.isdigit():
index = int(part)
if index < len(temp):
temp = temp[index]
else:
temp = None
break
else:
temp = None
break
else:
temp = None
break
if isinstance(temp, (dict, list)):
continue
else:
break
if temp is not None:
self.update_environments(key, temp)
def substitute_jsonpath(self, response, json_path_dict):
"""
jsonpath 提取参数
Args:
response: 响应结果
json_path_dict: {"k": "$.data", "x": "$.data[0].age[3].a"}
response = response
if not isinstance(response, (dict, str, list)):
logger.error(f"| 被提取对象非字典、非字符串、非列表不执行jsonpath提取被提取对象: {response}")
return {}
if regex and keys:
self.substitute_regex(response, regex, keys)
response = response if isinstance(response, (dict, list)) else json.loads(response)
if deps:
self.substitute_route(response, deps)
if jp_dict:
self.substitute_jsonpath(response, jp_dict)
Returns: 字符串或者list
def substitute_regex(self, response, regex, keys):
"""
使用正则表达式
Args:
response:
regex: 正则表达式r'"id": (\d+), "name": "(\w+)",'
keys:结果键列表["a", "b"],
Returns:
"""
json_path_dict = json_path_dict if isinstance(json_path_dict, dict) else json.loads(json_path_dict)
for key, expression in json_path_dict.items():
try:
parsed_expression = parse(expression)
data = response
# 使用解析器对象进行匹配和提取
match = parsed_expression.find(data)
result = [m.value for m in match]
self.update_environments(key, result[0]) if len(result) == 1 else self.update_environments(key,
result)
except Exception as e:
logger.error(f"jsonpath表达式错误'{expression}': {e}")
"""
response = json.dumps(response) if isinstance(response, (dict, list)) else response
match = re.search(regex, response)
if not match:
return {}
groups = match.groups()
for i, key in enumerate(keys):
try:
self.update_environments(key, groups[i])
except:
self.update_environments(key, None)
def substitute_route(self, response, route_str):
"""
字典取值
Args:
response:
route_str:
Returns:
"""
deps_list = re.sub(f"[\r\n]+", "", route_str).split(";")
for dep_item in deps_list:
key, value_path = dep_item.split("=")
value_path_parts = re.findall(r'\w+', value_path)
temp = response
for part in value_path_parts:
if isinstance(temp, dict):
temp = temp.get(part)
elif isinstance(temp, list):
if part.isdigit():
index = int(part)
if index < len(temp):
temp = temp[index]
else:
temp = None
break
else:
temp = None
break
else:
temp = None
break
if isinstance(temp, (dict, list)):
continue
else:
break
if temp is not None:
self.update_environments(key, temp)
def substitute_jsonpath(self, response, json_path_dict):
"""
jsonpath取值
Args:
response:
json_path_dict: {"k": "$.data", "x": "$.data[0].age[3].a"}
Returns: 字符串或者list
"""
json_path_dict = json_path_dict if isinstance(json_path_dict, dict) else json.loads(json_path_dict)
for key, expression in json_path_dict.items():
try:
parsed_expression = parse(expression)
data = response
# 使用解析器对象进行匹配和提取
match = parsed_expression.find(data)
result = [m.value for m in match]
self.update_environments(key, result[0]) if len(result) == 1 else self.update_environments(key,
result)
except Exception as e:
logger.error(f"| jsonpath表达式错误'{expression}': {e}")
if __name__ == '__main__':
# 测试subs函数
res = '{"code": 1,"data": [{"id": 1, "name": "Alice", "age": [20, 21, 22, {"a": "b"}]}]}'
lists = {"k": "$..code", "x": "$.data[0].age[3].a"}
dep_str = "name=data[0].name;ok=data[0].id;an=data[0].age"
regex_str = r'"id": (\d+), "name": "(\w+)",'
regex_key = ["a", "b"]
t = DataExtractor()
t.substitute_data(res, regex=regex_str, keys=regex_key, deps=dep_str, jp_dict=lists)
print("-------->res:", t.get_environments())
# 测试subs函数
res = '{"code": 1,"data": [{"id": 1, "name": "Alice", "age": [20, 21, 22, {"a": "b"}]}]}'
lists = {"k": "$..code", "x": "$.data[0].age[3].a"}
dep_str = "name=data[0].name;ok=data[0].id;an=data[0].age"
regex_str = r'"id": (\d+), "name": "(\w+)",'
regex_key = ["a", "b"]
t = DataExtractor()
t.substitute_data(res, regex=regex_str, keys=regex_key, deps=dep_str, jp_dict=lists)
print("-------->res:", t.get_environments())

View File

@ -14,10 +14,7 @@ from common.data_extraction.data_extractor import DataExtractor
class DependentParameter(DataExtractor):
"""
该类用于替换接口参数它会从字符串中寻找需要替换的参数并将其替换为关联参数表中对应的值
然后它将替换后的字符串转化为字典并返回如果找不到需要替换的参数则直接返回原始字符串
"""
"""数据更换"""
def __init__(self):
super().__init__()

View File

@ -35,7 +35,7 @@ class MysqlClient:
self.conn = self.pool.connection()
self.cursor = self.conn.cursor(DictCursor)
except Exception as e:
logger.error(f"数据库链接失败: {e}")
logger.error(f"| 数据库链接失败: {e}")
# raise
# @logger.log_decorator()
@ -72,9 +72,9 @@ class MysqlClient:
for method, sql_data in sql.items():
execute_method = getattr(self, f"_execute_{method}", None)
if not execute_method:
logger.error("sql字典集编写格式不符合规范")
raise ValueError("Invalid SQL method")
logger.info(f"执行 sql 语句集: {sql_data}")
logger.error("| sql字典集编写格式不符合规范")
raise ValueError("| Invalid SQL method")
logger.info(f"| 执行 sql 语句集: {sql_data}")
execute_method(sql_data)
self.cursor.close()
@ -83,7 +83,7 @@ class MysqlClient:
return self.result
except Exception as e:
logger.error(f"数据库操作异常: {e}")
logger.error(f"| 数据库操作异常: {e}")
raise
def _execute_write(self, sql_data):
@ -94,7 +94,7 @@ class MysqlClient:
try:
self.cursor.execute(str(sql_))
except Exception as err:
logger.error(f"执行 SQL 异常: {sql_}")
logger.error(f"| 执行 SQL 异常: {sql_}")
raise err
self.cursor.connection.commit()
@ -118,7 +118,7 @@ class MysqlClient:
self.cursor.execute(sql_)
self.result[sql_name] = self.cursor.fetchall()
except Exception as err:
logger.error(f"查询异常 sql: {sql_}")
logger.error(f"| 查询异常 sql: {sql_}")
raise err

View File

@ -15,12 +15,12 @@ from common.file_handling import logger
@singleton
class DoExcel:
def __init__(self, file_name):
self.file_name = file_name
self.wb = load_workbook(self.file_name)
self.init_sheet = self.wb["init"]
# def __enter__(self):
# self.wb = load_workbook(self.file_name)
# self.init_sheet = self.wb['init']
@ -29,7 +29,7 @@ class DoExcel:
# def __exit__(self, exc_type, exc_val, exc_tb):
# self.wb.save(self.file_name)
# self.wb.close()
def do_excel_yield(self):
"""
读取excel数据
@ -50,7 +50,7 @@ class DoExcel:
sub_data[first_header[k - 1]] = sheet.cell(i, k).value
sub_data["sheet"] = sheet_name
yield sub_data
@logger.log_decorator()
def write_back(self, sheet_name, i, **kwargs):
"""
@ -71,7 +71,7 @@ class DoExcel:
sheet.cell(i + 1, 25).value = test_result
sheet.cell(i + 1, 26).value = assert_log
self.wb.save(self.file_name)
@logger.log_decorator()
def clear_date(self):
"""
@ -80,7 +80,7 @@ class DoExcel:
"""
sheets = eval(self.get_excel_init().get("sheets"))
for sheet_name in sheets:
sheet = self.wb[sheet_name]
max_row = sheet.max_row # 获取最大行
@ -90,7 +90,7 @@ class DoExcel:
sheet.cell(i, 26).value = ""
self.wb.save(self.file_name)
return f"清空指定 {sheets} 中的单元格成功"
@logger.log_decorator()
def get_excel_init(self):
"""
@ -109,7 +109,7 @@ class DoExcel:
if init.get("run").upper() == "YES":
break
return init
def get_excel_init_and_cases(self):
"""
@ -122,18 +122,20 @@ class DoExcel:
init_data = self.get_excel_init()
databases = init_data.get('databases')
initialize_data = eval(init_data.get("initialize_data"))
host = init_data.get('host', "") + init_data.get("path", "")
host = init_data.get('host', "")
path = init_data.get("path", "")
host_path = host if host is not None else "" + path if path is not None else ""
except Exception as e:
raise e
return test_case, databases, initialize_data, host
return test_case, databases, initialize_data, host_path
def close_excel(self):
self.wb.close()
if __name__ == '__main__':
from config import Config
file_n = Config.test_case
excel = DoExcel(file_n)
# excel.get_excel_init()

View File

@ -17,13 +17,13 @@ from common.utils.decorators import request_retry_on_exception
class Pyt(LoadModulesFromFolder):
session = requests.Session()
def __init__(self):
super().__init__()
self.request = None
self.response = None
self.response_json = None
@request_retry_on_exception()
def http_client(self, host, url, method, **kwargs):
"""
@ -36,20 +36,21 @@ class Pyt(LoadModulesFromFolder):
"""
# 关闭 https 警告信息
urllib3.disable_warnings()
self.request = None
self.response = None
if not url:
raise ValueError("URL cannot be None")
__url = f'{host}{url}' if not re.match(r"https?", url) else url
# 增加兼容
# 处理 headers 参数为字符串类型的情况
if 'headers' in kwargs and isinstance(kwargs['headers'], str):
kwargs['headers'] = json.loads(kwargs['headers'])
# 处理 json 参数为字符串类型的情况
if 'json' in kwargs and isinstance(kwargs['json'], str):
kwargs['json'] = json.loads(kwargs['json'])
# 处理 files 参数的情况
fs = []
if 'files' in kwargs:
@ -67,7 +68,7 @@ class Pyt(LoadModulesFromFolder):
('file', (f'{file_path}', f, file_type))
)
kwargs['files'] = files
# 发送请求
self.request = requests.Request(method, __url, **kwargs)
self.response = self.session.send(self.request.prepare(), timeout=30, verify=True)

View File

@ -11,111 +11,111 @@ from time import perf_counter
from loguru import logger
from config import Config
from common.utils.decorators import singleton
from config import Config
LOG_DIR = Config.log_path
@singleton
class MyLogger:
"""
根据时间文件大小切割日志
"""
def __init__(self, log_dir=LOG_DIR, max_size=20, retention='7 days'):
self.log_dir = log_dir
self.max_size = max_size
self.retention = retention
self.logger = self.configure_logger()
def configure_logger(self):
"""
Returns:
"""
# 创建日志目录
os.makedirs(self.log_dir, exist_ok=True)
shared_config = {
"level": "ERROR",
"enqueue": True,
"backtrace": False,
"format": "{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
}
# 添加按照日期和大小切割的文件 handler
logger.add(
sink=f"{self.log_dir}/{{time:YYYY-MM-DD}}.log",
rotation=f"{self.max_size} MB",
retention=self.retention,
**shared_config
)
# 配置按照等级划分的文件 handler 和控制台输出
logger.add(sink=self.get_log_path, **shared_config)
return logger
def get_log_path(self, message: str) -> str:
"""
根据等级返回日志路径
Args:
message:
Returns:
"""
log_level = message.record["level"].name.lower()
log_file = f"{log_level}.log"
log_path = os.path.join(self.log_dir, log_file)
return log_path
def __getattr__(self, level: str):
return getattr(self.logger, level)
def log_decorator(self, msg="快看,异常了,别唧唧哇哇,快排查!!"):
"""
日志装饰器记录函数的名称参数返回值运行时间和异常信息
Args:
logger: 日志记录器对象
Returns:
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# self.logger.info(f'-----------分割线-----------')
self.logger.info(f'| 调用函数: {func.__name__} | args: {args} kwargs:{kwargs}')
start = perf_counter() # 开始时间
try:
result = func(*args, **kwargs)
end = perf_counter() # 结束时间
duration = end - start
self.logger.info(f"| 结束调用函数: {func.__name__}, duration{duration:4f}s")
return result
except Exception as e:
self.logger.error(f"| called {func.__name__} | error: {msg}: {e}")
# self.logger.info(f"-----------分割线-----------")
return wrapper
return decorator
"""
根据时间文件大小切割日志
"""
def __init__(self, log_dir=LOG_DIR, max_size=20, retention='7 days'):
self.log_dir = log_dir
self.max_size = max_size
self.retention = retention
self.logger = self.configure_logger()
def configure_logger(self):
"""
Returns:
"""
# 创建日志目录
os.makedirs(self.log_dir, exist_ok=True)
shared_config = {
"level": "ERROR",
"enqueue": True,
"backtrace": False,
"format": "{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
}
# 添加按照日期和大小切割的文件 handler
logger.add(
sink=f"{self.log_dir}/{{time:YYYY-MM-DD}}.log",
rotation=f"{self.max_size} MB",
retention=self.retention,
**shared_config
)
# 配置按照等级划分的文件 handler 和控制台输出
logger.add(sink=self.get_log_path, **shared_config)
return logger
def get_log_path(self, message: str) -> str:
"""
根据等级返回日志路径
Args:
message:
Returns:
"""
log_level = message.record["level"].name.lower()
log_file = f"{log_level}.log"
log_path = os.path.join(self.log_dir, log_file)
return log_path
def __getattr__(self, level: str):
return getattr(self.logger, level)
def log_decorator(self, msg="快看,异常了,别唧唧哇哇,快排查!!"):
"""
日志装饰器记录函数的名称参数返回值运行时间和异常信息
Args:
logger: 日志记录器对象
Returns:
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# self.logger.info(f'-----------分割线-----------')
# self.logger.info(f'| 调用函数: {func.__name__} | args: {args} kwargs:{kwargs}')
start = perf_counter() # 开始时间
try:
result = func(*args, **kwargs)
end = perf_counter() # 结束时间
duration = end - start
# self.logger.info(f"| 结束调用函数: {func.__name__}, duration{duration:4f}s")
return result
except Exception as e:
self.logger.error(f"| called {func.__name__} | error: {msg}: {e}")
# self.logger.info(f"-----------分割线-----------")
return wrapper
return decorator
if __name__ == '__main__':
log = MyLogger()
@log.log_decorator("勇哥也不知道错在哪里")
def test_zero_division_error(a, b):
return a / b
for i in range(1000):
log.error('错误信息')
log.critical('严重错误信息')
test_zero_division_error(1, 0)
log.debug('调试信息')
log.info('普通信息')
log.success('成功信息')
log.warning('警告信息')
log = MyLogger()
@log.log_decorator("勇哥也不知道错在哪里")
def test_zero_division_error(a, b):
return a / b
for i in range(1000):
log.error('错误信息')
log.critical('严重错误信息')
test_zero_division_error(1, 0)
log.debug('调试信息')
log.info('普通信息')
log.success('成功信息')
log.warning('警告信息')

View File

@ -41,7 +41,9 @@ class Action(Extractor, LoadScript, Validator, MysqlClient):
exec(compiled, {"pm": self})
except SyntaxError as e:
ExecuteDynamiCodeError(code, e)
except ExecuteDynamiCodeError as e:
except TypeError as e:
ExecuteDynamiCodeError(code, e)
except Exception as e:
ExecuteDynamiCodeError(code, e)
return self.variables
@ -54,22 +56,43 @@ class Action(Extractor, LoadScript, Validator, MysqlClient):
def variables(self, item):
self.__variables = item
# def update_variables(self, key, value):
# self.__variables[f"{{{{{key}}}}}"] = value
def analysis_request(self, request_data, headers_crypto, headers, request_crypto, extract_request_data):
# 请求头及body加密或者加签
headers, request_data = self.encrypt.encrypts(headers_crypto, headers, request_crypto, request_data)
# 提取请求参数信息
if extract_request_data is not None and request_data is not None:
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("responseTime", self.response.elapsed.total_seconds() * 1000) # 存响应时间
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:
self.logger.error(f"| 分析响应失败:{sheet}_{iid}_{name}_{desc}"
f"\nregex={regex};"
f" \nkeys={keys};"
f"\ndeps={deps};"
f"\njp_dict={jp_dict}"
f"\n{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"
self.logger.error(f"| exception case:**{sheet}_{iid}_{name}_{desc}**\n{e}")
raise AssertionFailedError(self.assertions)
finally:
print(f'| Assertion Result-->{self.assertions}')
response = self.response.text if self.response is not None else str(self.response)
# excel.write_back(sheet_name=sheet, i=iid, response=response, test_result=result,
# assert_log=str(self.assertions))
@staticmethod
def base_info(item):
"""
@ -139,7 +162,7 @@ class Action(Extractor, LoadScript, Validator, MysqlClient):
if sleep_time:
try:
time.sleep(sleep_time)
except MyBaseException as e:
except Exception as e:
raise MyBaseException(f"暂停时间必须是数字!")
def exc_sql(self, item):
@ -154,9 +177,9 @@ class Action(Extractor, LoadScript, Validator, MysqlClient):
if execute_sql_results and sql_params_dict:
try:
self.substitute_data(execute_sql_results, jp_dict=sql_params_dict)
except ParameterExtractionError as e:
except Exception as e:
ParameterExtractionError(sql_params_dict, str(e))
if __name__ == '__main__':
print(Action.__mro__)
print(Action())

View File

@ -37,20 +37,21 @@ def request_retry_on_exception(retries=2, delay=1.5):
nonlocal e
for i in range(retries):
try:
print(f"{i + 1}次发送请求的参数: {kwargs}")
print(f"| {i + 1}次发送请求的参数: {kwargs}")
response = func(*args, **kwargs)
print(f"请求地址 --> {response.request.url}")
print(f"请求头 --> {response.request.headers}")
print(f"请求 body --> {response.request.body}")
print(f"接口状态--> {response.status_code}")
print(f"接口耗时--> {response.elapsed}")
print(f"接口响应--> {response.text}")
print(f"| 请求地址 --> {response.request.url}")
print(f"| 请求头 --> {response.request.headers}")
print(f"| 请求 body --> {response.request.body}")
print(f"| 接口状态--> {response.status_code}")
print(f"| 接口耗时--> {response.elapsed}")
print(f"| 接口响应--> {response.text}")
except Exception as error:
e = error
time.sleep(delay)
else:
return response
raise Exception(f"请求重试**{retries}**次失败,请检查!!{e}")
raise Exception(f"| 请求重试**{retries}**次失败,请检查!!{e}")
return wrapper

View File

@ -12,7 +12,7 @@
class MyBaseException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
@ -20,7 +20,7 @@ class MyBaseException(Exception):
class RequestSendingError(MyBaseException):
"""请求异常"""
ERROR_CODE = 1001
def __init__(self, url, reason):
msg = f"请求异常URL={url}, 原因={reason}"
super().__init__(msg)
@ -29,7 +29,7 @@ class RequestSendingError(MyBaseException):
class DatabaseExceptionError(MyBaseException):
"""数据库异常"""
ERROR_CODE = 1002
def __init__(self, operation, reason):
msg = f"数据库异常:操作={operation}, 原因={reason}"
super().__init__(msg)
@ -38,7 +38,7 @@ class DatabaseExceptionError(MyBaseException):
class ParameterExtractionError(MyBaseException):
"""参数提取异常"""
ERROR_CODE = 1003
def __init__(self, parameter_path, reason):
msg = f"参数提取异常:参数路径={parameter_path}, 原因={reason}"
super().__init__(msg)
@ -47,7 +47,7 @@ class ParameterExtractionError(MyBaseException):
class ParameterReplacementError(MyBaseException):
"""参数替换异常"""
ERROR_CODE = 1004
def __init__(self, parameter_name, reason):
msg = f"参数替换异常:参数名称={parameter_name}, 原因={reason}"
super().__init__(msg)
@ -56,16 +56,18 @@ class ParameterReplacementError(MyBaseException):
class AssertionFailedError(MyBaseException):
"""断言异常"""
ERROR_CODE = 1005
def __init__(self, assertion_name, actual_value, expected_value):
msg = f"断言失败:断言名称={assertion_name}, 实际值={actual_value}, 期望值={expected_value}"
def __init__(self, assertion):
msg = f"断言失败:{assertion}"
print(msg)
super().__init__(msg)
class ExecuteDynamiCodeError(MyBaseException):
"""执行动态代码异常"""
ERROR_CODE = 1006
def __init__(self, code, reason):
msg = f"执行动态代码异常:动态代码={code}, 原因={reason}"
print(msg)
super().__init__(msg)

View File

@ -4,7 +4,7 @@
# Name: comparator_dict.py
# Description: 比较器名词释义
# Author: kira
# EMAIL: 2419352654@qq.com
# EMAIL: 262667641@qq.com
# Date: 2020/10/29 16:51
# -------------------------------------------------------------------------------

View File

@ -12,274 +12,274 @@ import re
def eq(actual_value, expect_value):
"""
实际值与期望值相等
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value == expect_value
"""
实际值与期望值相等
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value == expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def lt(actual_value, expect_value):
"""
实际值小于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value < expect_value
"""
实际值小于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value < expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def lte(actual_value, expect_value):
"""
实际值小于或等于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value <= expect_value
"""
实际值小于或等于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value <= expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def gt(actual_value, expect_value):
"""
实际值大于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value > expect_value
"""
实际值大于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value > expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def gte(actual_value, expect_value):
"""
实际值大于或等于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value >= expect_value
"""
实际值大于或等于期望值
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value >= expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def neq(actual_value, expect_value):
"""
实际值与期望值不相等
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value != expect_value
"""
实际值与期望值不相等
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert actual_value != expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def str_eq(actual_value, expect_value):
"""
字符串实际值与期望值相同
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert str(actual_value) == str(expect_value)
"""
字符串实际值与期望值相同
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert str(actual_value) == str(expect_value), f"预期结果:{actual_value},实际结果:{expect_value}"
def length_eq(actual_value, expect_value):
"""
实际值的长度等于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,))
assert len(actual_value) == expect_value
"""
实际值的长度等于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert len(actual_value) == expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def length_gt(actual_value, expect_value):
"""
实际值的长度大于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,))
assert len(actual_value) > expect_value
"""
实际值的长度大于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert len(actual_value) > expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def length_gte(actual_value, expect_value):
"""
实际值的长度大于或等于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,))
assert len(actual_value) >= expect_value
"""
实际值的长度大于或等于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert len(actual_value) >= expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def length_lt(actual_value, expect_value):
"""
实际值的长度小于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,))
assert len(actual_value) < expect_value
"""
实际值的长度小于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert len(actual_value) < expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def length_lte(actual_value, expect_value):
"""
实际值的长度小于或等于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,))
assert len(actual_value) <= expect_value
"""
实际值的长度小于或等于期望长度
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (int,)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert len(actual_value) <= expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def contains(actual_value, expect_value):
"""
期望值包含在实际值中
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(actual_value, (list, tuple, dict, str, bytes))
assert expect_value in actual_value
"""
期望值包含在实际值中
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(actual_value, (list, tuple, dict, str, bytes)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert expect_value in actual_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def contained_by(actual_value, expect_value):
"""
实际值被包含在期望值中
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (list, tuple, dict, str, bytes))
assert actual_value in expect_value
"""
实际值被包含在期望值中
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert isinstance(expect_value, (list, tuple, dict, str, bytes)), f"预期结果:{actual_value},实际结果:{expect_value}"
assert actual_value in expect_value, f"预期结果:{actual_value},实际结果:{expect_value}"
def type_match(actual_value, expect_value):
"""
实际值的类型与期望值的类型相匹配
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
def get_type(name):
if isinstance(name, type):
return name
elif isinstance(name, (str, bytes)):
try:
return __builtins__[name]
except KeyError:
raise ValueError(name)
else:
raise ValueError(name)
assert isinstance(actual_value, get_type(expect_value))
"""
实际值的类型与期望值的类型相匹配
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
def get_type(name):
if isinstance(name, type):
return name
elif isinstance(name, (str, bytes)):
try:
return __builtins__[name]
except KeyError:
raise ValueError(name)
else:
raise ValueError(name)
assert isinstance(actual_value, get_type(expect_value)), f"预期结果:{actual_value},实际结果:{expect_value}"
def regex_match(actual_value, expect_value):
"""
正则匹配(从字符串的起始位置匹配)
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
if not isinstance(actual_value, str):
actual_value = json.dumps(actual_value, ensure_ascii=False)
if not isinstance(expect_value, str):
expect_value = json.dumps(expect_value, ensure_ascii=False)
assert re.match(expect_value, actual_value)
"""
正则匹配(从字符串的起始位置匹配)
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
if not isinstance(actual_value, str):
actual_value = json.dumps(actual_value, ensure_ascii=False)
if not isinstance(expect_value, str):
expect_value = json.dumps(expect_value, ensure_ascii=False)
assert re.match(expect_value, actual_value), f"预期结果:{actual_value},实际结果:{expect_value}"
def regex_search(actual_value, expect_value):
"""
正则匹配(从字符串的任意位置匹配)
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
if not isinstance(actual_value, str):
actual_value = json.dumps(actual_value, ensure_ascii=False)
if not isinstance(expect_value, str):
expect_value = json.dumps(expect_value, ensure_ascii=False)
assert re.search(expect_value, actual_value)
"""
正则匹配(从字符串的任意位置匹配)
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
if not isinstance(actual_value, str):
actual_value = json.dumps(actual_value, ensure_ascii=False)
if not isinstance(expect_value, str):
expect_value = json.dumps(expect_value, ensure_ascii=False)
assert re.search(expect_value, actual_value), f"预期结果:{actual_value},实际结果:{expect_value}"
def startswith(actual_value, expect_value):
"""
实际值是以期望值开始
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert str(actual_value).startswith(str(expect_value))
"""
实际值是以期望值开始
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert str(actual_value).startswith(str(expect_value)), f"预期结果:{actual_value},实际结果:{expect_value}"
def endswith(actual_value, expect_value):
"""
实际值是以期望值结束
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert str(actual_value).endswith(str(expect_value))
"""
实际值是以期望值结束
Args:
actual_value: 实际值
expect_value: 期望值
Returns:
"""
assert str(actual_value).endswith(str(expect_value)), f"预期结果:{actual_value},实际结果:{expect_value}"

View File

@ -73,8 +73,8 @@ class Extractor:
Returns:
"""
logger.debug(f'正在执行数据提取:提取数据源内容:{resp_obj},{type(resp_obj)}')
logger.debug('正在执行数据提取:提取表达式:{expr}'.format(expr=expr))
# logger.debug(f'正在执行数据提取:提取数据源内容:{resp_obj},{type(resp_obj)}')
# logger.debug('正在执行数据提取:提取表达式:{expr}'.format(expr=expr))
try:
result = jsonpath.jsonpath(resp_obj if isinstance(resp_obj, (dict, list)) else json.dumps(resp_obj), expr)
except Exception as e:
@ -86,7 +86,7 @@ class Extractor:
elif isinstance(result, list):
if len(result) == 1:
result = result[0]
logger.info(f'提取成功,输出结果,提取表达式:{expr},提取结果:{result}')
# logger.info(f'提取成功,输出结果,提取表达式:{expr},提取结果:{result}')
return result

View File

@ -27,17 +27,17 @@ class LoadModulesFromFolder(DependentParameter):
if isinstance(folder_or_mnodule, str):
folder_path = folder_or_mnodule
if not os.path.exists(folder_path): # 检查文件夹路径是否存在
if not os.path.exists(folder_path):
raise ValueError("Folder path does not exist.")
for file_name in os.listdir(folder_path): # 遍历指定文件夹下的所有文件
for file_name in os.listdir(folder_path):
module_name, ext = os.path.splitext(file_name)
if ext == '.py' and module_name != '__init__':
module_path = os.path.join(folder_path, file_name) # 获取模块文件的完整路径
module_path = os.path.join(folder_path, file_name)
spec = importlib.util.spec_from_file_location(module_name, module_path)
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module) # 加载模块文件并执行其中的代码,将函数定义添加到 module 对象中
spec.loader.exec_module(module)
except:
continue
for name, obj in vars(module).items():

View File

@ -15,56 +15,56 @@ from common.validation import logger
class Loaders(Pyt):
def __init__(self):
super().__init__()
@logger.log_decorator()
def load_built_in_functions(self, model):
"""
加载bif_functions包中的内建方法
Returns:
"""
built_in_functions = {}
for name, item in vars(model).items():
if isinstance(item, types.FunctionType):
built_in_functions[name] = item
return built_in_functions
@staticmethod
@logger.log_decorator()
def load_built_in_comparators() -> object:
"""
加载包中的内建比较器
Returns:
"""
built_in_comparators = {}
for name, item in vars(comparators).items():
if isinstance(item, types.FunctionType):
built_in_comparators[name] = item
return built_in_comparators
@logger.log_decorator()
def set_bif_fun(self, model):
"""
将所有内置方法加载到依赖表中
Returns:
"""
for k, v in self.load_built_in_functions(model).items():
self.update_environments(f"{k}()", v)
def __init__(self):
super().__init__()
@logger.log_decorator()
def load_built_in_functions(self, model):
"""
加载bif_functions包中的内建方法
Returns:
"""
built_in_functions = {}
for name, item in vars(model).items():
if isinstance(item, types.FunctionType):
built_in_functions[name] = item
return built_in_functions
@staticmethod
@logger.log_decorator()
def load_built_in_comparators() -> object:
"""
加载包中的内建比较器
Returns:
"""
built_in_comparators = {}
for name, item in vars(comparators).items():
if isinstance(item, types.FunctionType):
built_in_comparators[name] = item
return built_in_comparators
@logger.log_decorator()
def set_bif_fun(self, model):
"""
加载内置方法
Returns:
"""
for k, v in self.load_built_in_functions(model).items():
self.update_environments(f"{k}()", v)
if __name__ == '__main__':
from common.bif_functions import bif_faker
import extensions
print()
loaders = Loaders()
loaders.load_built_in_comparators()
loaders.set_bif_fun(bif_faker)
print(loaders.get_environments())
loaders.set_bif_fun(extensions)
print(loaders.get_environments())
from common.bif_functions import bif_faker
import extensions
print()
loaders = Loaders()
loaders.load_built_in_comparators()
loaders.set_bif_fun(bif_faker)
print(loaders.get_environments())
loaders.set_bif_fun(extensions)
print(loaders.get_environments())

View File

@ -15,116 +15,116 @@ from common.validation.loaders import Loaders
class Validator(Loaders):
"""
校验器
主要功能
1格式化校验变量
2校验期望结果与实际结果与预期一致并返回校验结果
"""
validate_variables_list = []
assertions = []
def __init__(self):
super().__init__()
def uniform_validate(self, validate_variables):
"""
统一格式化测试用例的验证变量validate
Args:
validate_variables: 参数格式 listdict
示例
[{"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}]
or {"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}
"""
校验器
主要功能
1格式化校验变量
2校验期望结果与实际结果与预期一致并返回校验结果
"""
validate_variables_list = []
assertions = []
Returns: 返回数据格式 list
示例
[{"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}]
def __init__(self):
super().__init__()
"""
if isinstance(validate_variables, str):
validate_variables = json.loads(validate_variables)
if isinstance(validate_variables, list):
for item in validate_variables:
self.uniform_validate(item)
elif isinstance(validate_variables, dict):
if "check" in validate_variables.keys() and "expect" in validate_variables.keys():
# 如果验证mapping中不包含comparator时默认为{"comparator": "eq"}
check_item = validate_variables.get("check")
expect_value = validate_variables.get("expect")
comparator = validate_variables.get("comparator", "eq")
self.validate_variables_list.append({
"check": check_item,
"expect": expect_value,
"comparator": comparator,
# "check_rt": check_rt
})
else:
logger.error("参数格式错误!")
def validate(self, resp=None):
"""
校验期望结果与实际结果与预期一致
Args:
resp: ResponseObject对象实例
def uniform_validate(self, validate_variables):
"""
统一格式化测试用例的验证变量validate
Args:
validate_variables: 参数格式 listdict
示例
[{"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}]
or {"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}
Returns:
Returns: 返回数据格式 list
示例
[{"check":"result.user.name","comparator":"eq","expect":"chenyongzhi"}]
"""
validate_result = "通过"
for validate_variable in self.validate_variables_list:
check_item = validate_variable['check']
expect_value = validate_variable['expect']
comparator = validate_variable['comparator']
actual_value = Extractor.extract_value_by_jsonpath(resp_obj=resp, expr=check_item)
try:
# 获取比较器
fun = self.load_built_in_comparators()[comparator]
fun(actual_value=actual_value, expect_value=expect_value)
except (AssertionError, TypeError) as e:
validate_result = "失败"
raise e
finally:
self.assertions.append({
'检查项': check_item,
'期望值': expect_value,
'实际值': actual_value,
'断言方法': comparator_dict.get(comparator),
"断言结果": validate_result
})
def run_validate(self, validate_variables, resp=None):
"""
统一格式化测试用例的验证变量validate然后校验期望结果与实际结果与预期一致
Args:
validate_variables:参数格式 listdict
resp:ResponseObject对象实例
"""
if isinstance(validate_variables, str):
validate_variables = json.loads(validate_variables)
if isinstance(validate_variables, list):
for item in validate_variables:
self.uniform_validate(item)
elif isinstance(validate_variables, dict):
if "check" in validate_variables.keys() and "expect" in validate_variables.keys():
# 如果验证mapping中不包含comparator时默认为{"comparator": "eq"}
check_item = validate_variables.get("check")
expect_value = validate_variables.get("expect")
comparator = validate_variables.get("comparator", "eq")
self.validate_variables_list.append({
"check": check_item,
"expect": expect_value,
"comparator": comparator
})
else:
logger.error("参数格式错误!")
Returns:返回校验结果
def validate(self, resp=None):
"""
校验期望结果与实际结果与预期一致
Args:
resp: ResponseObject对象实例
"""
if not validate_variables:
return ""
# 清空校验变量
self.validate_variables_list.clear()
self.assertions.clear()
self.uniform_validate(validate_variables)
if not self.validate_variables_list:
raise "uniform_validate 执行失败,无法进行 validate 校验"
self.validate(resp)
Returns:
"""
for validate_variable in self.validate_variables_list:
check_item = validate_variable['check']
expect_value = validate_variable['expect']
comparator = validate_variable['comparator']
if not check_item.startswith("$"):
actual_value = check_item
else:
actual_value = Extractor.extract_value_by_jsonpath(resp_obj=resp, expr=check_item)
try:
fun = self.load_built_in_comparators()[comparator]
fun(actual_value=actual_value, expect_value=expect_value)
validate_result = "通过"
except (AssertionError, TypeError) as e:
validate_result = "失败"
raise e
finally:
self.assertions.append({
'检查项': check_item,
'期望值': expect_value,
'实际值': actual_value,
'断言方法': comparator_dict.get(comparator),
"断言结果": validate_result
})
def run_validate(self, validate_variables, resp=None):
"""
统一格式化测试用例的验证变量validate然后校验期望结果与实际结果与预期一致
Args:
validate_variables:参数格式 listdict
resp:ResponseObject对象实例
Returns:返回校验结果
"""
if not validate_variables:
return ""
self.validate_variables_list.clear()
self.assertions.clear()
self.uniform_validate(validate_variables)
if not self.validate_variables_list:
raise "uniform_validate 执行失败,无法进行 validate 校验"
self.validate(resp)
if __name__ == '__main__':
validate_variables1 = {"check": "$.result.user.name", "comparator": "eq", "expect": "chenyongzhi"}
validate_variables2 = [
{"check": "code", "comparator": "eq", "expect": "200"},
{"check": "result.user.name", "comparator": "eq", "expect": "chenyongzhi"}
]
resp_obj = {"code": "200", "result": {"user": {"name": "chenyongzhi"}}}
validator = Validator()
validator.run_validate(validate_variables1, resp_obj)
print(validator.assertions)
validator.run_validate(validate_variables2, resp_obj)
print(validator.assertions)
validator.run_validate(validate_variables2, resp_obj)
print(validator.assertions)
validate_variables1 = {"check": "$.result.user.name", "comparator": "eq", "expect": "chenyongzhi"}
validate_variables2 = [
{"check": "$.code", "comparator": "eq", "expect": "200"},
{"check": "result.user.name", "comparator": "eq", "expect": "chenyongzhi"}
]
resp_obj = {"code": "200", "result": {"user": {"name": "chenyongzhi"}}}
validator = Validator()
validator.run_validate(validate_variables1, resp_obj)
print(validator.assertions)
validator.run_validate(validate_variables2, resp_obj)
print(validator.assertions)
validator.run_validate(validate_variables2, resp_obj)
print(validator.assertions)

View File

@ -9,8 +9,8 @@ class Config:
# 测试数据所在路径
# *****************************************************************
templates = os.path.join(base_path, "cases", "templates", "template.xlsx") # 模板文件
test_case = os.path.join(base_path, "cases", "cases", "test_api.xlsx")
# test_case = os.path.join(base_path, "cases", "cases", "test_cases.xlsx")
# test_case = os.path.join(base_path, "cases", "cases", "test_api.xlsx")
test_case = os.path.join(base_path, "cases", "cases", "test_cases.xlsx")
test_files = os.path.join(base_path, 'cases', 'files')
# *****************************************************************

View File

@ -7,3 +7,5 @@
@time: 2023/7/13 16:38
@desc:
"""
a = {"key": "value", "c": ""}
print(a.get("c", ""))

View File

@ -1,6 +1,9 @@
# 前置脚本代码
import json
def setup(pm):
print("pm---------------->", pm.get_variables())
print("pm---------------->", pm.variables())
# request_data = pm.get_variables() # 获取得到请求数据
"""
request_data 的值: {'Url': '/login',
@ -11,10 +14,10 @@ def setup(pm):
'Expected': None, 'Response': '', 'Assertion': '', 'Error Log': ''
}
"""
BSP_TOKEN = pm.get_environments("{{BSP_TOKEN}}") # 获取环境变量
pm.update_environments("BSP_TOKEN_NEWS", BSP_TOKEN + "修改了环境变量") # 设置环境变量
print("---->pm.get_environments", pm.get_environments("{{BSP_TOKEN_NEWS}}"))
print("---->pm.get_variables", pm.get_variables())
request = pm.variables()
email = json.loads(request.get("Request Data")).get("email")
pm.update_environments("email", email) # 设置环境变量
print("---->pm.get_environments", pm.get_environments("{{email}}"))
setup(pm)
@ -22,19 +25,19 @@ setup(pm)
# 后置脚本代码
def tear_down(pm):
vars_data = pm.get_environments("{{变量名称}}") # 获取环境变量
response = pm.get_variables() # 获取得到响应结果对象
# vars_data = pm.get_environments("{{变量名称}}") # 获取环境变量
response = pm.variables() # 获取得到响应结果对象
response.json()
print(f"请求地址 --> {response.request.url}")
print(f"请求头 --> {response.request.headers}")
print(f"请求 body --> {response.request.body}")
print(f"接口状态--> {response.status_code}")
print(f"接口耗时--> {response.elapsed}")
print(f"接口响应--> {response.text}")
# print(f"请求地址 --> {response.request.url}")
# print(f"请求头 --> {response.request.headers}")
# print(f"请求 body --> {response.request.body}")
# print(f"接口状态--> {response.status_code}")
# print(f"接口耗时--> {response.elapsed}")
# print(f"接口响应--> {response.text}")
token = response.json()['token']
pm.update_environments("BSP_TOKEN_NEWS", token + vars_data) # 重新设置环境变量
print("---->pm.get_environments", pm.get_environments("{{BSP_TOKEN_NEWS}}"))
print("---->pm.get_variables", pm.get_variables())
pm.update_environments("token", token) # 重新设置环境变量
# print("---->pm.get_environments", pm.get_environments("{{BSP_TOKEN_NEWS}}"))
# print("---->pm.get_variables", pm.get_variables())
tear_down(pm)

View File

@ -42,57 +42,22 @@ class TestProjectApi(unittest.TestCase):
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)
# 首执行 sql
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)
result = "PASS"
# 执行请求操作
kwargs = {request_data_type: request_data, 'headers': headers, "params": query_str}
self.action.send_request(host, url, method, teardown_script, **kwargs)
try:
# 提取响应
self.action.substitute_data(self.action.response_json, regex=regex, keys=keys, deps=deps, jp_dict=jp_dict)
except Exception as err:
self.action.logger.error(f"提取响应失败:{sheet}_{iid}_{name}_{desc}"
f"\nregex={regex};"
f" \nkeys={keys};"
f"\ndeps={deps};"
f"\njp_dict={jp_dict}"
f"\n{err}")
# 修正断言
self.action.analysis_response(sheet, iid, name, desc, regex, keys, deps, jp_dict)
expected = self.action.replace_dependent_parameter(expected)
try:
# print(f"期望结果--> {expected}")
# 执行断言 返回结果元组
self.action.run_validate(expected, self.action.response_json)
except Exception as e:
result = "FAIL"
self.action.logger.error(f'异常用例: **{sheet}_{iid}_{name}_{desc}**\n{e}')
raise e
finally:
print(f"断言结果-->", self.action.assertions)
response = self.action.response.text if self.action.response is not None else str(self.action.response)
# 响应结果及测试结果回写 excel
excel.write_back(sheet_name=sheet, i=iid, response=response, test_result=result,
assert_log=str(self.action.assertions))
self.action.execute_validation(excel, sheet, iid, name, desc, expected)
@classmethod
def tearDownClass(cls) -> None: