refactor: 重构执行引擎为上下文驱动架构

- 优化 WorkflowExecutor 与 Exchange支持 ExecutionEnv 资源注入。
 - 实现 Session 级别连接复用与变量池内存镜像化,消除重复 I/O 开销。
 - 引入 ChainMap 实现动态上下文切换,解决参数化变量与全局提取变量的优先级覆盖。
 - 完善变量提取与断言逻辑,确保跨用例变量流转的可靠性。
This commit is contained in:
2026-03-14 11:45:52 +08:00
parent 2116016a0d
commit 00791809df
9 changed files with 276 additions and 289 deletions

View File

@@ -8,11 +8,12 @@
import logging
import importlib
from typing import Any, List, Optional
from collections import ChainMap
from pydantic import TypeAdapter
from core import settings
from core.models import CaseInfo, ValidateItem, RequestModel, ApiActionModel
from core.context import ExecutionEnv
from core.models import RawSchema, ValidateItem, HttpAction, ApiActionModel
from core.session import Session
from core.exchange import Exchange
from utils.case_validator import CaseValidator
@@ -24,61 +25,54 @@ VALIDATE_LIST_ADAPTER = TypeAdapter(List[ValidateItem])
class WorkflowExecutor:
def __init__(self, session: Session, exchanger: Exchange):
self.session = session
self.exchanger = exchanger
def perform(self, case_info: CaseInfo,context: Optional[dict[str, Any]] = None) -> Any:
@classmethod
def perform(cls, case_info: RawSchema, env: ExecutionEnv, context: Optional[dict[str, Any]] = None) -> Any:
"""执行单个用例支持直接请求和PO模式调用"""
context = context or {}
# --- 重点 1备份并切换上下文 ---
# 保存 Exchange 当前的全局字典引用
original_cache = env.exchanger.global_vars
# 1. 建立优先级变量池 (参数化变量 > 全局提取变量)
# ChainMap 是实现“局部覆盖全局”性能最好的方案
combined_vars = ChainMap(context, original_cache)
# 将 Exchange 的内部缓存临时指向这个合并池
env.exchanger.global_vars = combined_vars
# 1. 局部变量优先级注入
# 备份全局缓存,将当前行数据合并进去
old_cache = self.exchanger._variable_cache.copy()
self.exchanger._variable_cache.update(context)
resp = None # 初始化 resp避免异常时引用未定义
try:
# 2. 动态更新标题(如果 context 中包含 title
current_title = context.get("title") or case_info.title
logger.info(f"🚀 执行用例: {current_title}")
# raw_data = case_info.model_dump(by_alias=True, exclude_none=True)
# 1. 变量替换(将 ${var} 替换为真实值)
# rendered_dict = self.exchanger.replace(raw_data)
# rendered_case = CaseInfo.model_validate(rendered_dict)
raw_action_dict = case_info.action.model_dump(by_alias=True, exclude_none=True)
rendered_action_dict = env.exchanger.replace(raw_action_dict)
# --- 2. 决定执行模式 ---
if case_info.is_po_mode():
# PO 模式:仅渲染 api_action
action_dict = case_info.api_action.model_dump(by_alias=True, exclude_none=True)
rendered_action_dict = self.exchanger.replace(action_dict)
# 重新校验以修复类型(如 params 里的 int
rendered_action = ApiActionModel.model_validate(rendered_action_dict)
# PO 模式:反射调用
resp = self._execute_po_method(action=rendered_action)
resp = cls._execute_po_method(rendered_action, env)
else:
# 接口模式:直接请求
# 直接将 RequestModel 转为字典传给 session.request
request_kwargs = case_info.request.model_dump(by_alias=True, exclude_none=True)
rendered_req_dict = self.exchanger.replace(request_kwargs)
rendered_request = RequestModel.model_validate(rendered_req_dict)
rendered_request = HttpAction.model_validate(rendered_action_dict)
request_kwargs = rendered_request.model_dump(by_alias=True, exclude_none=True)
resp = self.session.request(**request_kwargs)
resp = env.session.request(**request_kwargs)
# --- 3. 后处理 (提取 & 断言) ---
self._post_process(resp, case_info)
# --- 3. 后处理:提取与断言 ---
cls._post_process(resp, case_info, env, original_cache)
return resp
except Exception as e:
logger.error(f"用例执行失败: {case_info.title} | 原因: {e}", exc_info=True)
raise
finally:
# 4. 关键:清理现场,还原全局变量池
self.exchanger._variable_cache = old_cache
# 兜底确保环境还原 (尽管 try 块中已经还原了一次,这里确保异常情况下也复位)
env.exchanger.global_vars = original_cache
def _execute_po_method(self, action: ApiActionModel):
@staticmethod
def _execute_po_method(action: ApiActionModel, env: ExecutionEnv):
"""核心反射逻辑:根据字符串动态加载 api/ 目录下的类并执行方法"""
class_name = action.api_class
method_name = action.method
@@ -94,15 +88,13 @@ class WorkflowExecutor:
try:
# 1. 动态导入模块(假设都在 api 目录下)
# 例如 class_name 是 UserAPI则尝试从 api.user 导入
# 这里简单处理,你可以根据你的文件名约定进一步优化逻辑
# module_name = f"api.{class_name.lower().replace('api', '')}"
module = importlib.import_module(module_name)
# 2. 获取类并实例化
cls = getattr(module, class_name)
api_instance = cls(self.session) # 传入 session 保持会话统一
api_instance = cls(env.session) # 传入 session 保持会话统一
# 3. 调用方法并返回结果
method = getattr(api_instance, method_name)
@@ -118,21 +110,33 @@ class WorkflowExecutor:
logger.error(f"反射调用失败: {class_name}.{method_name} -> {e}")
raise
def _post_process(self, resp: Any, rendered_case: CaseInfo):
# 3. 提取变量 (接口关联)
if rendered_case.extract:
for var_name, extract_info in rendered_case.extract.items():
self.exchanger.extract(resp, var_name, *extract_info)
@classmethod
def _post_process(cls, resp: Any, case_info: RawSchema, env: ExecutionEnv, original_cache: dict):
"""
统一后处理逻辑:处理变量提取(写全局)和断言校验(读局部+全局)
"""
# 记录当前的混合上下文 (ChainMap),供断言使用
combined_vars = env.exchanger.global_vars
# 4. 断言校验
if rendered_case.validate_data:
# raw_validate_list = [i.model_dump(by_alias=True) for i in rendered_case.validate_data]
# 1. 变量提取 (Write Operation)
if case_info.extract:
try:
# 必须切回 original_cache 才能持久化写入到全局变量池
env.exchanger.global_vars = original_cache
for var_name, extract_info in case_info.extract.items():
env.exchanger.extract(resp, var_name, *extract_info)
finally:
# 提取完成后,切回 combined_vars防止后续逻辑如断言丢失局部变量上下文
env.exchanger.global_vars = combined_vars
# 2. 断言校验 (Read Operation)
if case_info.validate_data:
raw_validate_list = [
item.model_dump(by_alias=True) if isinstance(item, ValidateItem) else item
for item in rendered_case.validate_data
for item in case_info.validate_data
]
rendered_validate_list = self.exchanger.replace(raw_validate_list)
rendered_validate_list = env.exchanger.replace(raw_validate_list)
# 重新通过 Adapter 触发类型修复 (str -> int)
final_validate_data = VALIDATE_LIST_ADAPTER.validate_python(rendered_validate_list)
CaseValidator.validate(resp, final_validate_data)