feat(executor): 重构用例加载与执行逻辑,支持参数化变量优先级
- 引入 CaseEntity 包装器,实现数据模型与执行上下文解耦。 - 移除加载阶段的 deepcopy,优化大规模参数化用例的内存占用。 - 实现 perform 阶段的局部变量注入,确保参数化数据优先级高于全局缓存。
This commit is contained in:
@@ -13,6 +13,7 @@ import logging
|
||||
|
||||
import allure
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from core import settings
|
||||
from core.executor import WorkflowExecutor
|
||||
from core.session import Session
|
||||
@@ -30,6 +31,12 @@ session = Session(settings.base_url)
|
||||
exchanger = Exchange(settings.DATA_DIR / "extract.yaml") # 指向 data/extract.yaml
|
||||
executor = WorkflowExecutor(session, exchanger)
|
||||
|
||||
@dataclass
|
||||
class CaseEntity:
|
||||
"""用例执行实体:解耦模型数据与执行上下文"""
|
||||
step_data: CaseInfo
|
||||
row_context: dict[str, Any]
|
||||
|
||||
|
||||
class TestTemplateBase:
|
||||
"""
|
||||
@@ -56,12 +63,12 @@ class CaseDataLoader:
|
||||
yield from base_path.rglob("test_*.yaml")
|
||||
|
||||
@classmethod
|
||||
def load_cases(cls, file_path: Path) -> List[CaseInfo]:
|
||||
def load_cases(cls, file_path: Path) -> List[CaseEntity]:
|
||||
"""
|
||||
加载单个 YAML 文件并转化为 CaseInfo 列表
|
||||
包含参数化数据的自动拆解逻辑
|
||||
"""
|
||||
cases = []
|
||||
entities = []
|
||||
try:
|
||||
# 1. 使用重构后的 YamlProcessor 加载原始字典
|
||||
processor = FileHandle(file_path)
|
||||
@@ -69,13 +76,24 @@ class CaseDataLoader:
|
||||
|
||||
if not raw_data:
|
||||
return []
|
||||
# 1. 提取参数化数据
|
||||
parametrize_data = raw_data.pop("parametrize", None)
|
||||
|
||||
# 2. 实例化唯一的模板对象 (Pydantic 校验)
|
||||
# 此时占位符 ${var} 会被 SmartInt/SmartDict 校验器放行
|
||||
template_case = CaseInfo(**raw_data)
|
||||
|
||||
# 2. 检查是否存在参数化字段
|
||||
if "parametrize" in raw_data and isinstance(raw_data["parametrize"], list):
|
||||
cases.extend(cls._parse_parametrize(raw_data))
|
||||
if parametrize_data and isinstance(parametrize_data, list) and len(parametrize_data) >= 2:
|
||||
# 3. 参数化拆分
|
||||
headers = parametrize_data[0]
|
||||
for row in parametrize_data[1:]:
|
||||
row_map = dict(zip(headers, row))
|
||||
# 包装为实体,存入引用而非副本
|
||||
entities.append(CaseEntity(step_data=template_case, row_context=row_map))
|
||||
else:
|
||||
# 3. 普通单条用例封装
|
||||
cases.append(CaseInfo(**raw_data))
|
||||
# 普通用例,上下文为空
|
||||
entities.append(CaseEntity(step_data=template_case, row_context={}))
|
||||
|
||||
except YamlLoadError:
|
||||
# YamlProcessor 已经记录了 error 日志,这里直接跳过
|
||||
@@ -85,7 +103,7 @@ class CaseDataLoader:
|
||||
except Exception as e:
|
||||
logger.error(f"加载用例发生未知异常 [{file_path.name}]: {e}")
|
||||
|
||||
return cases
|
||||
return entities
|
||||
|
||||
@staticmethod
|
||||
def _parse_parametrize(raw_data: dict[str, Any]) -> List[CaseInfo]:
|
||||
@@ -121,9 +139,10 @@ class CaseDataLoader:
|
||||
return case_list
|
||||
|
||||
@classmethod
|
||||
def get_all_cases(cls, cases_dir: Union[str, Path]) -> List[CaseInfo]:
|
||||
def get_all_cases(cls, cases_dir: Union[str, Path]) -> List[CaseEntity]:
|
||||
"""
|
||||
全量获取接口:供 CaseGenerator 调用
|
||||
全量获取接口:供 CaseGenerator 调用 frank
|
||||
|
||||
"""
|
||||
all_cases = []
|
||||
for file in cls.fetch_yaml_files(cases_dir):
|
||||
@@ -165,6 +184,8 @@ class CaseGenerator:
|
||||
#
|
||||
# # 4. 挂载
|
||||
# method_name = f"test_{file_path.stem}"
|
||||
case_title = case_info.row_context.get("title") or case_info.step_data.title
|
||||
|
||||
method_name = f"test_case_{index}_{case_info.title[:20]}"
|
||||
safe_name = "".join([c if c.isalnum() else "_" for c in method_name])
|
||||
# setattr(target_cls, method_name, dynamic_test_method)
|
||||
@@ -173,8 +194,10 @@ class CaseGenerator:
|
||||
|
||||
@staticmethod
|
||||
# def _create_case_method(case_template: Dict, fields: List[str], values: List[Any], ids: List[str]):
|
||||
def _create_case_method(case_template: CaseInfo):
|
||||
def _create_case_method(entity: CaseEntity):
|
||||
"""封装具体的 pytest 执行节点"""
|
||||
case_template = entity.step_data
|
||||
context = entity.row_context
|
||||
|
||||
# 预取 Allure 层级信息
|
||||
# epic = case_template.get("epic", settings.allure_epic)
|
||||
@@ -194,7 +217,7 @@ class CaseGenerator:
|
||||
# current_params = dict(zip(fields, case_args))
|
||||
# case_exec_data = {**case_template, **current_params}
|
||||
# case_title = current_params.get("title", "未命名用例")
|
||||
case_title = case_template.title or "未命名用例"
|
||||
case_title = context.get("title") or case_template.title or "未命名用例"
|
||||
|
||||
# 日志记录 (利用 instance 标注来源)
|
||||
logger.info(f"🚀 [Runner] Class: {instance.__class__.__name__} | Case: {case_title}")
|
||||
@@ -202,7 +225,7 @@ class CaseGenerator:
|
||||
# 执行与断言
|
||||
allure.dynamic.title(case_title)
|
||||
# executor.perform(case_exec_data)
|
||||
executor.perform(case_template)
|
||||
executor.perform(case_template,context=context)
|
||||
|
||||
# 手动链路装饰 (Allure)
|
||||
# run_actual_case = allure.epic(epic)(run_actual_case)
|
||||
|
||||
Reference in New Issue
Block a user