802 lines
31 KiB
Python
802 lines
31 KiB
Python
"""
|
||
测试编排器模块
|
||
|
||
负责组合API解析器、API调用器、验证器和规则执行器,进行端到端的API测试
|
||
"""
|
||
|
||
import logging
|
||
import json
|
||
import time
|
||
from typing import Dict, List, Any, Optional, Union, Tuple
|
||
from enum import Enum
|
||
import datetime
|
||
|
||
from .input_parser.parser import InputParser, YAPIEndpoint, SwaggerEndpoint, ParsedYAPISpec, ParsedSwaggerSpec
|
||
from .api_caller.caller import APICaller, APIRequest, APIResponse
|
||
from .json_schema_validator.validator import JSONSchemaValidator
|
||
from .rule_repository.repository import RuleRepository
|
||
from .rule_executor.executor import RuleExecutor
|
||
from .models.rule_models import RuleQuery, TargetType, RuleCategory, RuleLifecycle, RuleScope
|
||
from .models.config_models import RuleRepositoryConfig, RuleStorageConfig
|
||
|
||
class TestResult:
|
||
"""测试结果类"""
|
||
|
||
class Status(str, Enum):
|
||
"""测试状态枚举"""
|
||
PASSED = "通过"
|
||
FAILED = "失败"
|
||
ERROR = "错误"
|
||
SKIPPED = "跳过"
|
||
|
||
def __init__(self,
|
||
endpoint_id: str,
|
||
endpoint_name: str,
|
||
status: Status,
|
||
message: str = "",
|
||
api_request: Optional[APIRequest] = None,
|
||
api_response: Optional[APIResponse] = None,
|
||
validation_details: Optional[Dict[str, Any]] = None,
|
||
elapsed_time: float = 0.0):
|
||
"""
|
||
初始化测试结果
|
||
|
||
Args:
|
||
endpoint_id: API端点ID(通常是方法+路径的组合)
|
||
endpoint_name: API端点名称
|
||
status: 测试状态
|
||
message: 测试结果消息
|
||
api_request: API请求对象
|
||
api_response: API响应对象
|
||
validation_details: 验证详情
|
||
elapsed_time: 执行耗时(秒)
|
||
"""
|
||
self.endpoint_id = endpoint_id
|
||
self.endpoint_name = endpoint_name
|
||
self.status = status
|
||
self.message = message
|
||
self.api_request = api_request
|
||
self.api_response = api_response
|
||
self.validation_details = validation_details or {}
|
||
self.elapsed_time = elapsed_time
|
||
self.timestamp = datetime.datetime.now()
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
"""将测试结果转换为字典"""
|
||
result = {
|
||
"endpoint_id": self.endpoint_id,
|
||
"endpoint_name": self.endpoint_name,
|
||
"status": self.status,
|
||
"message": self.message,
|
||
"elapsed_time": self.elapsed_time,
|
||
"timestamp": self.timestamp.isoformat(),
|
||
}
|
||
|
||
if self.api_request:
|
||
result["api_request"] = {
|
||
"method": self.api_request.method,
|
||
"url": str(self.api_request.url),
|
||
"params": self.api_request.params,
|
||
"body": self.api_request.json_data
|
||
}
|
||
|
||
if self.api_response:
|
||
result["api_response"] = {
|
||
"status_code": self.api_response.status_code,
|
||
"content": self.api_response.json_content if self.api_response.json_content else str(self.api_response.content),
|
||
"elapsed_time": self.api_response.elapsed_time
|
||
}
|
||
|
||
if self.validation_details:
|
||
result["validation_details"] = self.validation_details
|
||
|
||
return result
|
||
|
||
class TestSummary:
|
||
"""测试结果摘要"""
|
||
|
||
def __init__(self):
|
||
"""初始化测试结果摘要"""
|
||
self.total = 0
|
||
self.passed = 0
|
||
self.failed = 0
|
||
self.error = 0
|
||
self.skipped = 0
|
||
self.start_time = datetime.datetime.now()
|
||
self.end_time: Optional[datetime.datetime] = None
|
||
self.results: List[TestResult] = []
|
||
|
||
def add_result(self, result: TestResult):
|
||
"""添加测试结果"""
|
||
self.total += 1
|
||
|
||
if result.status == TestResult.Status.PASSED:
|
||
self.passed += 1
|
||
elif result.status == TestResult.Status.FAILED:
|
||
self.failed += 1
|
||
elif result.status == TestResult.Status.ERROR:
|
||
self.error += 1
|
||
elif result.status == TestResult.Status.SKIPPED:
|
||
self.skipped += 1
|
||
|
||
self.results.append(result)
|
||
|
||
def finalize(self):
|
||
"""完成测试,记录结束时间"""
|
||
self.end_time = datetime.datetime.now()
|
||
|
||
@property
|
||
def duration(self) -> float:
|
||
"""测试持续时间(秒)"""
|
||
if not self.end_time:
|
||
return 0.0
|
||
|
||
return (self.end_time - self.start_time).total_seconds()
|
||
|
||
@property
|
||
def success_rate(self) -> float:
|
||
"""测试成功率"""
|
||
if self.total == 0:
|
||
return 0.0
|
||
|
||
return self.passed / self.total * 100
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
"""将测试结果摘要转换为字典"""
|
||
return {
|
||
"total": self.total,
|
||
"passed": self.passed,
|
||
"failed": self.failed,
|
||
"error": self.error,
|
||
"skipped": self.skipped,
|
||
"success_rate": f"{self.success_rate:.2f}%",
|
||
"start_time": self.start_time.isoformat(),
|
||
"end_time": self.end_time.isoformat() if self.end_time else None,
|
||
"duration": f"{self.duration:.2f}秒",
|
||
"results": [result.to_dict() for result in self.results]
|
||
}
|
||
|
||
def to_json(self, pretty=True) -> str:
|
||
"""将测试结果摘要转换为JSON字符串"""
|
||
indent = 2 if pretty else None
|
||
return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)
|
||
|
||
def print_summary(self):
|
||
"""打印测试结果摘要"""
|
||
print(f"\n测试结果摘要:")
|
||
print(f"总测试数: {self.total}")
|
||
print(f"通过: {self.passed}")
|
||
print(f"失败: {self.failed}")
|
||
print(f"错误: {self.error}")
|
||
print(f"跳过: {self.skipped}")
|
||
print(f"成功率: {self.success_rate:.2f}%")
|
||
print(f"总耗时: {self.duration:.2f}秒")
|
||
|
||
class APITestOrchestrator:
|
||
"""API测试编排器"""
|
||
|
||
def __init__(self, base_url: str, rule_repo_path: str = "./rules"):
|
||
"""
|
||
初始化API测试编排器
|
||
|
||
Args:
|
||
base_url: API基础URL
|
||
rule_repo_path: 规则库路径
|
||
"""
|
||
self.base_url = base_url.rstrip('/')
|
||
self.logger = logging.getLogger(__name__)
|
||
|
||
# 初始化组件
|
||
self.parser = InputParser()
|
||
self.api_caller = APICaller()
|
||
self.validator = JSONSchemaValidator()
|
||
|
||
# 初始化规则库和规则执行器
|
||
rule_config = RuleRepositoryConfig(
|
||
storage=RuleStorageConfig(path=rule_repo_path)
|
||
)
|
||
self.rule_repo = RuleRepository(rule_config)
|
||
self.rule_executor = RuleExecutor(self.rule_repo)
|
||
|
||
def _build_api_request(self, endpoint: Union[YAPIEndpoint, SwaggerEndpoint]) -> Tuple[APIRequest, Dict[str, Any]]:
|
||
"""
|
||
构建API请求对象
|
||
|
||
Args:
|
||
endpoint: API端点对象
|
||
|
||
Returns:
|
||
Tuple[APIRequest, Dict[str, Any]]: API请求对象和测试数据
|
||
"""
|
||
# 获取端点信息
|
||
if hasattr(endpoint, 'method'):
|
||
method = endpoint.method
|
||
else:
|
||
method = "GET" # 默认方法
|
||
|
||
if hasattr(endpoint, 'path'):
|
||
path = endpoint.path
|
||
else:
|
||
path = "/" # 默认路径
|
||
|
||
# 替换路径中的参数占位符
|
||
path_params = {}
|
||
if "{" in path and "}" in path:
|
||
# 查找路径中的所有参数
|
||
import re
|
||
param_matches = re.findall(r'\{([^}]+)\}', path)
|
||
|
||
for param in param_matches:
|
||
# 生成一个随机值作为参数
|
||
path_params[param] = f"test_{param}"
|
||
|
||
# 查找请求参数
|
||
params = {}
|
||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||
body = None
|
||
|
||
# YAPI端点特有属性
|
||
if hasattr(endpoint, 'req_headers') and endpoint.req_headers:
|
||
for header in endpoint.req_headers:
|
||
if 'name' in header and 'value' in header:
|
||
headers[header['name']] = header['value']
|
||
|
||
if hasattr(endpoint, 'req_query') and endpoint.req_query:
|
||
for query in endpoint.req_query:
|
||
if 'name' in query:
|
||
params[query['name']] = query.get('value', '')
|
||
|
||
if hasattr(endpoint, 'req_body_type') and endpoint.req_body_type == 'json' and hasattr(endpoint, 'req_body_other'):
|
||
try:
|
||
# 如果req_body_other是JSON字符串,它可能包含请求体schema
|
||
req_body_schema = json.loads(endpoint.req_body_other) if isinstance(endpoint.req_body_other, str) else None
|
||
if req_body_schema and isinstance(req_body_schema, dict):
|
||
# 使用schema生成请求体
|
||
body = self._generate_data_from_schema(req_body_schema)
|
||
else:
|
||
# 如果不是有效的schema,使用它作为请求体示例
|
||
body = req_body_schema
|
||
except json.JSONDecodeError:
|
||
# 如果解析失败,使用一个默认的请求体
|
||
self.logger.warning(f"无法解析YAPI请求体JSON: {endpoint.req_body_other}")
|
||
body = {"test": "data"}
|
||
|
||
# Swagger端点特有属性
|
||
if hasattr(endpoint, 'parameters') and endpoint.parameters:
|
||
for param in endpoint.parameters:
|
||
param_in = param.get('in', '')
|
||
param_name = param.get('name', '')
|
||
param_schema = param.get('schema', {})
|
||
|
||
if not param_name:
|
||
continue
|
||
|
||
# 生成参数值,优先使用example
|
||
param_value = param.get('example', None)
|
||
if param_value is None and param_schema:
|
||
param_value = self._generate_data_from_schema(param_schema)
|
||
|
||
if param_value is None:
|
||
# 使用默认值
|
||
param_value = param.get('default', 'test_value')
|
||
|
||
if param_in == 'query':
|
||
params[param_name] = param_value
|
||
elif param_in == 'header':
|
||
headers[param_name] = str(param_value)
|
||
elif param_in == 'path' and param_name in path_params:
|
||
path_params[param_name] = str(param_value)
|
||
|
||
if hasattr(endpoint, 'request_body') and endpoint.request_body:
|
||
content = endpoint.request_body.get('content', {})
|
||
json_content = content.get('application/json', {})
|
||
|
||
if 'example' in json_content:
|
||
body = json_content['example']
|
||
elif 'schema' in json_content:
|
||
# 基于schema创建请求体
|
||
body = self._generate_data_from_schema(json_content['schema'])
|
||
|
||
# 构建完整URL(替换路径参数)
|
||
url = self.base_url + path
|
||
for param, value in path_params.items():
|
||
url = url.replace(f"{{{param}}}", str(value))
|
||
|
||
# 创建API请求
|
||
request = None
|
||
try:
|
||
request = APIRequest(
|
||
method=method,
|
||
url=url,
|
||
headers=headers,
|
||
params=params,
|
||
json_data=body
|
||
)
|
||
except Exception as e:
|
||
self.logger.error(f"创建API请求时发生错误: {e}")
|
||
raise e
|
||
|
||
# 执行请求准备阶段的规则
|
||
endpoint_id = f"{method.upper()} {path}"
|
||
|
||
# 创建规则执行上下文
|
||
context = {
|
||
'api_request': request,
|
||
'endpoint_id': endpoint_id,
|
||
'endpoint': endpoint,
|
||
'path_params': path_params,
|
||
'query_params': params,
|
||
'headers': headers,
|
||
'body': body
|
||
}
|
||
|
||
# 执行请求准备阶段的规则
|
||
rule_results = self.rule_executor.execute_rules_for_lifecycle(
|
||
lifecycle=RuleLifecycle.REQUEST_PREPARATION,
|
||
context=context
|
||
)
|
||
|
||
# 保存规则执行结果,以便在测试结果中使用
|
||
self.last_request_rule_results = rule_results
|
||
|
||
# 保存请求对象,以便在响应验证时使用
|
||
self.last_request = request
|
||
|
||
# 收集测试数据
|
||
test_data = {
|
||
"path_params": path_params,
|
||
"query_params": params,
|
||
"headers": headers,
|
||
"body": body,
|
||
"rule_results": [
|
||
{
|
||
"rule_id": result.rule.id,
|
||
"rule_name": result.rule.name,
|
||
"is_valid": result.is_valid,
|
||
"message": result.message
|
||
} for result in rule_results
|
||
]
|
||
}
|
||
|
||
return request, test_data
|
||
|
||
def _validate_response(self, response: APIResponse, endpoint: Union[YAPIEndpoint, SwaggerEndpoint]) -> Dict[str, Any]:
|
||
"""
|
||
验证API响应
|
||
|
||
Args:
|
||
response: API响应对象
|
||
endpoint: API端点对象
|
||
|
||
Returns:
|
||
Dict[str, Any]: 验证结果
|
||
"""
|
||
validation_results = {
|
||
"status_code": {
|
||
"is_valid": 200 <= response.status_code < 300,
|
||
"expected": "2XX",
|
||
"actual": response.status_code
|
||
},
|
||
"json_format": {
|
||
"is_valid": response.json_content is not None,
|
||
"message": "响应应为有效的JSON格式" if response.json_content is None else "响应是有效的JSON格式"
|
||
}
|
||
}
|
||
|
||
# 尝试从API定义中提取响应schema进行验证
|
||
schema = None
|
||
schema_source = "未知"
|
||
|
||
# 从YAPI定义中提取响应schema
|
||
if hasattr(endpoint, 'res_body') and endpoint.res_body and response.json_content:
|
||
try:
|
||
# YAPI中的res_body通常是JSON字符串格式的schema
|
||
if isinstance(endpoint.res_body, str) and endpoint.res_body.strip():
|
||
schema = json.loads(endpoint.res_body)
|
||
schema_source = "YAPI响应定义"
|
||
except json.JSONDecodeError:
|
||
self.logger.warning(f"无法解析YAPI响应schema: {endpoint.res_body}")
|
||
|
||
# 从Swagger定义中提取响应schema
|
||
elif hasattr(endpoint, 'responses') and endpoint.responses and response.json_content:
|
||
# Swagger中通常以状态码为key,包含schema定义
|
||
success_responses = endpoint.responses.get('200', {}) or endpoint.responses.get('201', {})
|
||
if not success_responses and any(str(k).startswith('2') for k in endpoint.responses.keys()):
|
||
# 尝试查找任何2xx响应
|
||
for k in endpoint.responses.keys():
|
||
if str(k).startswith('2'):
|
||
success_responses = endpoint.responses[k]
|
||
break
|
||
|
||
if success_responses:
|
||
schema_obj = None
|
||
if 'schema' in success_responses:
|
||
schema_obj = success_responses['schema']
|
||
elif 'content' in success_responses and 'application/json' in success_responses['content']:
|
||
schema_obj = success_responses['content']['application/json'].get('schema')
|
||
|
||
if schema_obj:
|
||
schema = schema_obj
|
||
schema_source = "Swagger响应定义"
|
||
|
||
# 使用提取的schema进行验证
|
||
if schema and response.json_content:
|
||
try:
|
||
result = self.validator.validate(response.json_content, schema)
|
||
validation_results["schema_validation"] = {
|
||
"source": schema_source,
|
||
"is_valid": result.is_valid,
|
||
"errors": result.errors if not result.is_valid else []
|
||
}
|
||
except Exception as e:
|
||
self.logger.error(f"验证响应时发生错误: {str(e)}")
|
||
validation_results["schema_validation"] = {
|
||
"source": schema_source,
|
||
"is_valid": False,
|
||
"errors": [f"验证过程中发生错误: {str(e)}"]
|
||
}
|
||
|
||
# 如果我们有JSON Schema规则,可以验证响应体
|
||
endpoint_id = ""
|
||
if hasattr(endpoint, 'path'):
|
||
path = endpoint.path
|
||
method = getattr(endpoint, 'method', "GET")
|
||
endpoint_id = f"{method.upper()} {path}"
|
||
|
||
schema_rules = self.rule_repo.get_rules_for_target(
|
||
target_type=TargetType.API_RESPONSE,
|
||
target_id=endpoint_id
|
||
)
|
||
|
||
if schema_rules:
|
||
# 使用找到的第一个规则
|
||
from .models.rule_models import JSONSchemaDefinition
|
||
for schema_rule in schema_rules:
|
||
if isinstance(schema_rule, JSONSchemaDefinition):
|
||
# 验证响应体
|
||
if response.json_content:
|
||
result = self.validator.validate_with_rule(response.json_content, schema_rule)
|
||
validation_results[f"rule_schema_validation_{schema_rule.id}"] = {
|
||
"source": f"规则库 ({schema_rule.id})",
|
||
"is_valid": result.is_valid,
|
||
"errors": result.errors if not result.is_valid else []
|
||
}
|
||
|
||
# 使用规则执行器验证规则
|
||
# 创建执行上下文
|
||
if hasattr(endpoint, 'path'):
|
||
api_request = None
|
||
if hasattr(self, 'last_request'):
|
||
api_request = self.last_request
|
||
|
||
context = {
|
||
'api_response': response,
|
||
'api_request': api_request,
|
||
'endpoint_id': endpoint_id,
|
||
'endpoint': endpoint
|
||
}
|
||
|
||
# 执行响应验证阶段的规则
|
||
rule_results = self.rule_executor.execute_rules_for_lifecycle(
|
||
lifecycle=RuleLifecycle.RESPONSE_VALIDATION,
|
||
context=context
|
||
)
|
||
|
||
# 将规则执行结果添加到验证结果中
|
||
for i, rule_result in enumerate(rule_results):
|
||
validation_results[f"rule_execution_{i}"] = {
|
||
"rule_id": rule_result.rule.id,
|
||
"rule_name": rule_result.rule.name,
|
||
"is_valid": rule_result.is_valid,
|
||
"message": rule_result.message,
|
||
"details": rule_result.details
|
||
}
|
||
|
||
# 基本验证: 检查返回码、响应时间等
|
||
validation_results["response_time"] = {
|
||
"value": response.elapsed_time,
|
||
"message": f"响应时间: {response.elapsed_time:.4f}秒"
|
||
}
|
||
|
||
return validation_results
|
||
|
||
def run_test_for_endpoint(self, endpoint: Union[YAPIEndpoint, SwaggerEndpoint]) -> TestResult:
|
||
"""
|
||
运行单个API端点的测试
|
||
|
||
Args:
|
||
endpoint: API端点对象
|
||
|
||
Returns:
|
||
TestResult: 测试结果
|
||
"""
|
||
# 获取端点信息
|
||
endpoint_id = f"{getattr(endpoint, 'method', 'GET')} {getattr(endpoint, 'path', '/')}"
|
||
endpoint_name = getattr(endpoint, 'title', '') or getattr(endpoint, 'summary', '') or endpoint_id
|
||
|
||
self.logger.info(f"测试端点: {endpoint_id} - {endpoint_name}")
|
||
|
||
try:
|
||
# 构建API请求
|
||
request, test_data = self._build_api_request(endpoint)
|
||
|
||
# 检查请求准备阶段的规则验证结果
|
||
request_rule_failures = []
|
||
for rule_result in test_data.get("rule_results", []):
|
||
if not rule_result.get("is_valid", True):
|
||
request_rule_failures.append(f"{rule_result.get('rule_name', '未知规则')}: {rule_result.get('message', '验证失败')}")
|
||
|
||
# 如果有关键性的请求验证失败,可以选择跳过API调用
|
||
if request_rule_failures and any("严重错误" in failure for failure in request_rule_failures):
|
||
return TestResult(
|
||
endpoint_id=endpoint_id,
|
||
endpoint_name=endpoint_name,
|
||
status=TestResult.Status.FAILED,
|
||
message=f"请求准备阶段验证失败: {'; '.join(request_rule_failures)}",
|
||
api_request=request,
|
||
api_response=None,
|
||
validation_details={"request_rule_failures": request_rule_failures},
|
||
elapsed_time=0.0
|
||
)
|
||
|
||
# 发送请求
|
||
start_time = time.time()
|
||
response = self.api_caller.call_api(request)
|
||
elapsed_time = time.time() - start_time
|
||
|
||
# 验证响应
|
||
validation_results = self._validate_response(response, endpoint)
|
||
|
||
# 执行请求后处理规则
|
||
context = {
|
||
'api_request': request,
|
||
'api_response': response,
|
||
'endpoint_id': endpoint_id,
|
||
'endpoint': endpoint,
|
||
'elapsed_time': elapsed_time
|
||
}
|
||
|
||
post_rule_results = self.rule_executor.execute_rules_for_lifecycle(
|
||
lifecycle=RuleLifecycle.POST_VALIDATION,
|
||
context=context
|
||
)
|
||
|
||
# 将后处理规则结果添加到验证结果中
|
||
for i, rule_result in enumerate(post_rule_results):
|
||
validation_results[f"post_rule_execution_{i}"] = {
|
||
"rule_id": rule_result.rule.id,
|
||
"rule_name": rule_result.rule.name,
|
||
"is_valid": rule_result.is_valid,
|
||
"message": rule_result.message,
|
||
"details": rule_result.details
|
||
}
|
||
|
||
# 判断测试是否通过
|
||
# 检查所有验证结果是否有失败的
|
||
rule_failures = []
|
||
validation_failures = []
|
||
|
||
for key, result in validation_results.items():
|
||
if isinstance(result, dict) and 'is_valid' in result and not result['is_valid']:
|
||
if key.startswith('rule_execution_') or key.startswith('post_rule_execution_'):
|
||
rule_name = result.get('rule_name', '未知规则')
|
||
rule_message = result.get('message', '验证失败')
|
||
rule_failures.append(f"{rule_name}: {rule_message}")
|
||
else:
|
||
validation_failures.append(result.get('message', f"{key}验证失败"))
|
||
|
||
# 合并请求规则失败和响应规则失败
|
||
all_rule_failures = request_rule_failures + rule_failures
|
||
|
||
# 决定测试结果状态
|
||
if not validation_failures and not all_rule_failures:
|
||
# 所有验证和规则都通过
|
||
result = TestResult(
|
||
endpoint_id=endpoint_id,
|
||
endpoint_name=endpoint_name,
|
||
status=TestResult.Status.PASSED,
|
||
message="API测试通过",
|
||
api_request=request,
|
||
api_response=response,
|
||
validation_details=validation_results,
|
||
elapsed_time=elapsed_time
|
||
)
|
||
elif not validation_failures and all_rule_failures:
|
||
# 基本验证通过,但规则验证失败
|
||
result = TestResult(
|
||
endpoint_id=endpoint_id,
|
||
endpoint_name=endpoint_name,
|
||
status=TestResult.Status.FAILED,
|
||
message=f"API规则验证失败: {'; '.join(all_rule_failures)}",
|
||
api_request=request,
|
||
api_response=response,
|
||
validation_details=validation_results,
|
||
elapsed_time=elapsed_time
|
||
)
|
||
self.logger.error(f"接口{endpoint_id} 规则验证失败: {'; '.join(all_rule_failures)}")
|
||
else:
|
||
# 基本验证失败
|
||
result = TestResult(
|
||
endpoint_id=endpoint_id,
|
||
endpoint_name=endpoint_name,
|
||
status=TestResult.Status.FAILED,
|
||
message=f"API测试失败: {'; '.join(validation_failures)}",
|
||
api_request=request,
|
||
api_response=response,
|
||
validation_details=validation_results,
|
||
elapsed_time=elapsed_time
|
||
)
|
||
self.logger.error(f"接口{endpoint_id} 测试失败: {'; '.join(validation_failures)}")
|
||
|
||
return result
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"测试端点 {endpoint_id} 时发生错误: {str(e)}")
|
||
return TestResult(
|
||
endpoint_id=endpoint_id,
|
||
endpoint_name=endpoint_name,
|
||
status=TestResult.Status.ERROR,
|
||
message=f"测试执行错误: {str(e)}",
|
||
elapsed_time=0.0
|
||
)
|
||
|
||
def run_tests_from_yapi(self, yapi_file_path: str, categories: Optional[List[str]] = None) -> TestSummary:
|
||
"""
|
||
从YAPI定义文件运行API测试
|
||
|
||
Args:
|
||
yapi_file_path: YAPI定义文件路径
|
||
categories: 要测试的API分类列表(如果为None,则测试所有分类)
|
||
|
||
Returns:
|
||
TestSummary: 测试结果摘要
|
||
"""
|
||
# 解析YAPI文件
|
||
self.logger.info(f"从YAPI文件加载API定义: {yapi_file_path}")
|
||
parsed_yapi = self.parser.parse_yapi_spec(yapi_file_path)
|
||
|
||
if not parsed_yapi:
|
||
self.logger.error(f"解析YAPI文件失败: {yapi_file_path}")
|
||
|
||
# 创建一个空的测试摘要
|
||
summary = TestSummary()
|
||
summary.finalize()
|
||
return summary
|
||
|
||
# 筛选端点
|
||
endpoints = parsed_yapi.endpoints
|
||
if categories:
|
||
endpoints = [endpoint for endpoint in endpoints if endpoint.category_name in categories]
|
||
|
||
# 运行测试
|
||
summary = TestSummary()
|
||
|
||
for endpoint in endpoints:
|
||
result = self.run_test_for_endpoint(endpoint)
|
||
summary.add_result(result)
|
||
|
||
summary.finalize()
|
||
return summary
|
||
|
||
def run_tests_from_swagger(self, swagger_file_path: str, tags: Optional[List[str]] = None) -> TestSummary:
|
||
"""
|
||
从Swagger定义文件运行API测试
|
||
|
||
Args:
|
||
swagger_file_path: Swagger定义文件路径
|
||
tags: 要测试的API标签列表(如果为None,则测试所有标签)
|
||
|
||
Returns:
|
||
TestSummary: 测试结果摘要
|
||
"""
|
||
# 解析Swagger文件
|
||
self.logger.info(f"从Swagger文件加载API定义: {swagger_file_path}")
|
||
parsed_swagger = self.parser.parse_swagger_spec(swagger_file_path)
|
||
|
||
if not parsed_swagger:
|
||
self.logger.error(f"解析Swagger文件失败: {swagger_file_path}")
|
||
|
||
# 创建一个空的测试摘要
|
||
summary = TestSummary()
|
||
summary.finalize()
|
||
return summary
|
||
|
||
# 筛选端点
|
||
endpoints = parsed_swagger.endpoints
|
||
if tags:
|
||
endpoints = [endpoint for endpoint in endpoints if any(tag in endpoint.tags for tag in tags)]
|
||
|
||
# 运行测试
|
||
summary = TestSummary()
|
||
|
||
for endpoint in endpoints:
|
||
result = self.run_test_for_endpoint(endpoint)
|
||
summary.add_result(result)
|
||
|
||
summary.finalize()
|
||
return summary
|
||
|
||
def _generate_data_from_schema(self, schema: Dict[str, Any]) -> Any:
|
||
"""
|
||
根据JSON Schema生成测试数据
|
||
|
||
Args:
|
||
schema: JSON Schema
|
||
|
||
Returns:
|
||
生成的测试数据
|
||
"""
|
||
if not schema:
|
||
return None
|
||
|
||
schema_type = schema.get('type')
|
||
|
||
if schema_type == 'object':
|
||
result = {}
|
||
properties = schema.get('properties', {})
|
||
|
||
for prop_name, prop_schema in properties.items():
|
||
# 首先检查是否有example或default值
|
||
if 'example' in prop_schema:
|
||
result[prop_name] = prop_schema['example']
|
||
elif 'default' in prop_schema:
|
||
result[prop_name] = prop_schema['default']
|
||
else:
|
||
# 递归生成子属性的值
|
||
result[prop_name] = self._generate_data_from_schema(prop_schema)
|
||
|
||
return result
|
||
|
||
elif schema_type == 'array':
|
||
# 为数组生成一个样本项
|
||
items_schema = schema.get('items', {})
|
||
# 默认生成1个元素,对于测试来说通常足够
|
||
return [self._generate_data_from_schema(items_schema)]
|
||
|
||
elif schema_type == 'string':
|
||
# 处理不同的字符串格式
|
||
string_format = schema.get('format', '')
|
||
|
||
if string_format == 'date':
|
||
return '2023-01-01'
|
||
elif string_format == 'date-time':
|
||
return '2023-01-01T12:00:00Z'
|
||
elif string_format == 'email':
|
||
return 'test@example.com'
|
||
elif string_format == 'uuid':
|
||
return '00000000-0000-0000-0000-000000000000'
|
||
elif 'enum' in schema:
|
||
# 如果有枚举值,选择第一个
|
||
return schema['enum'][0] if schema['enum'] else 'enum_value'
|
||
elif 'pattern' in schema:
|
||
# 如果有正则表达式模式,返回一个简单的符合模式的字符串
|
||
# 注意:这里只是一个简单处理,不能处理所有正则表达式
|
||
return f"pattern_{schema['pattern']}_value"
|
||
else:
|
||
return 'test_string'
|
||
|
||
elif schema_type == 'number' or schema_type == 'integer':
|
||
# 处理数值类型
|
||
if 'minimum' in schema and 'maximum' in schema:
|
||
# 如果有最小值和最大值,取中间值
|
||
return (schema['minimum'] + schema['maximum']) / 2
|
||
elif 'minimum' in schema:
|
||
return schema['minimum']
|
||
elif 'maximum' in schema:
|
||
return schema['maximum']
|
||
elif schema_type == 'integer':
|
||
return 1
|
||
else:
|
||
return 1.0
|
||
|
||
elif schema_type == 'boolean':
|
||
return True
|
||
|
||
elif schema_type == 'null':
|
||
return None
|
||
|
||
# 如果是复杂类型或未知类型,返回一个默认值
|
||
return 'test_value'
|
||
|
||
|
||
# python run_api_tests.py --base-url http://127.0.0.1:4523/m1/6386850-6083489-default --yapi assets/doc/井筒API示例.json |