This commit is contained in:
gongwenxin
2025-05-21 18:31:26 +08:00
parent 2b1afbf47e
commit 0e3e721bc0
9 changed files with 443 additions and 345 deletions
+60 -1
View File
@@ -93,16 +93,18 @@ class BaseAPITestCase:
use_llm_for_query_params: bool = False
use_llm_for_headers: bool = False
def __init__(self, endpoint_spec: Dict[str, Any], global_api_spec: Dict[str, Any]):
def __init__(self, endpoint_spec: Dict[str, Any], global_api_spec: Dict[str, Any], json_schema_validator: Optional[Any] = None):
"""
初始化测试用例。
Args:
endpoint_spec: 当前被测API端点的详细定义 (来自YAPI/Swagger解析结果)。
global_api_spec: 完整的API规范文档 (来自YAPI/Swagger解析结果)。
json_schema_validator: APITestOrchestrator 传入的 JSONSchemaValidator 实例 (可选)。
"""
self.endpoint_spec = endpoint_spec
self.global_api_spec = global_api_spec
self.logger = logging.getLogger(f"testcase.{self.id}")
self.json_schema_validator = json_schema_validator # 存储传入的校验器实例
self.logger.debug(f"Test case '{self.id}' initialized for endpoint: {self.endpoint_spec.get('method', '')} {self.endpoint_spec.get('path', '')}")
# --- 1. 请求生成与修改阶段 ---
@@ -118,6 +120,21 @@ class BaseAPITestCase:
self.logger.debug(f"Hook: generate_request_body, current body type: {type(current_body)}")
return current_body
# --- 1.5. 请求URL修改阶段 (新增钩子) ---
def modify_request_url(self, current_url: str) -> str:
"""
允许测试用例在请求发送前修改最终构建的URL。
默认不进行任何修改。
Args:
current_url: 由编排器构建的当前请求URL (已包含base_url和路径参数)。
Returns:
最终要使用的URL。
"""
self.logger.debug(f"Hook: modify_request_url, original URL: {current_url}")
return current_url
# --- 2. 请求预校验阶段 ---
def validate_request_url(self, url: str, request_context: APIRequestContext) -> List[ValidationResult]:
self.logger.debug(f"Hook: validate_request_url, url: {url}")
@@ -141,6 +158,48 @@ class BaseAPITestCase:
self.logger.debug(f"Hook: check_performance, elapsed: {response_context.elapsed_time}")
return []
# --- Helper methods ---
def validate_data_against_schema(
self,
data_to_validate: Any,
schema_definition: Dict[str, Any],
context_message_prefix: str = "Data"
) -> List[ValidationResult]:
"""
使用注入的 JSONSchemaValidator 针对给定的 schema 验证数据。
Args:
data_to_validate: 要验证的数据 (通常是解析后的 JSON 对象)。
schema_definition: JSON Schema 定义字典。
context_message_prefix: 用于错误消息的上下文前缀。
Returns:
一个 ValidationResult 对象的列表。
"""
results = []
if not self.json_schema_validator:
self.logger.warning(f"JSONSchemaValidator 未注入到测试用例 '{self.id}'。无法执行 schema 验证。")
results.append(self.failed(f"{context_message_prefix} schema validation skipped: Validator not available."))
return results
is_valid, errors = self.json_schema_validator.validate(data_to_validate, schema_definition)
if is_valid:
results.append(self.passed(f"{context_message_prefix} conforms to the JSON schema."))
else:
error_messages = []
if isinstance(errors, list):
for error in errors: # jsonschema.exceptions.ValidationError 对象
error_messages.append(f"- Path: '{list(error.path)}', Message: {error.message}") # error.path 是一个deque
elif isinstance(errors, str): # 兼容旧版或简单错误字符串
error_messages.append(errors)
full_message = f"{context_message_prefix} does not conform to the JSON schema. Errors:\n" + "\n".join(error_messages)
results.append(self.failed(
message=full_message,
details={"schema_errors": error_messages, "validated_data_sample": str(data_to_validate)[:200]}
))
self.logger.warning(f"{context_message_prefix} schema validation failed: {full_message}")
return results
# --- Helper to easily create a passed ValidationResult ---
@staticmethod
def passed(message: str, details: Optional[Dict[str, Any]] = None) -> ValidationResult:
+26 -3
View File
@@ -665,7 +665,8 @@ class APITestOrchestrator:
try:
test_case_instance = test_case_class(
endpoint_spec=endpoint_spec_dict,
global_api_spec=global_api_spec_dict
global_api_spec=global_api_spec_dict,
json_schema_validator=self.validator # <--- 注入 JSONSchemaValidator
)
test_case_instance.logger.info(f"开始执行测试用例 '{test_case_instance.id}' for endpoint '{endpoint_spec_dict.get('method')} {endpoint_spec_dict.get('path')}'")
@@ -694,9 +695,26 @@ class APITestOrchestrator:
# 注意: 如果 _prepare_initial_request_data 填充的 final_url 已经包含了 base_url,这里的拼接逻辑需要调整
# 假设 final_url_template 只是 path string e.g. /users/{id}
# ---- 调用测试用例的 URL 修改钩子 ----
effective_url = final_url # 默认使用原始构建的URL
if hasattr(test_case_instance, 'modify_request_url') and callable(getattr(test_case_instance, 'modify_request_url')):
try:
modified_url_by_tc = test_case_instance.modify_request_url(final_url)
if modified_url_by_tc != final_url:
test_case_instance.logger.info(f"Test case '{test_case_instance.id}' modified URL from '{final_url}' to '{modified_url_by_tc}'")
effective_url = modified_url_by_tc # 使用测试用例修改后的URL
else:
test_case_instance.logger.debug(f"Test case '{test_case_instance.id}' did not modify the URL via modify_request_url hook.")
except Exception as e_url_mod:
test_case_instance.logger.error(f"Error in test case '{test_case_instance.id}' during modify_request_url: {e_url_mod}. Using original URL '{final_url}'.", exc_info=True)
# effective_url 保持为 final_url
else:
test_case_instance.logger.debug(f"Test case '{test_case_instance.id}' does not have a callable modify_request_url method. Using original URL.")
# ---- 结束 URL 修改钩子调用 ----
api_request_context = APIRequestContext(
method=method, # 使用从 _prepare_initial_request_data 获取的 method
url=final_url,
url=effective_url, # <--- 使用 effective_url
path_params=current_path_params,
query_params=current_q_params,
headers=current_headers,
@@ -1156,8 +1174,13 @@ class APITestOrchestrator:
global_api_spec=global_api_spec
)
endpoint_test_result.add_executed_test_case_result(executed_case_result)
if executed_case_result.status.value == TestResult.Status.FAILED.value:
# 红色
self.logger.debug(f"\033[91m ❌ 测试用例 '{tc_class.id}' 执行失败。\033[0m")
else:
self.logger.debug(f"\033[92m ✅ 测试用例 '{tc_class.id}' 执行成功。\033[0m")
self.logger.debug(f"测试用例 '{tc_class.id}' 执行完毕,状态: {executed_case_result.status.value}")
endpoint_test_result.finalize_endpoint_test()
self.logger.info(f"端点 '{endpoint_id}' 测试完成,最终状态: {endpoint_test_result.overall_status.value}")