Files
InterfaceAutoTest/core/creator.py
CNWei 69a96a0060 refactor(): 重构动态用例生成逻辑并解耦核心组件
- 将 `CaseGenerator` 拆分为 `CaseDataLoader`(数据加载)和 `CaseGenerator`(用例构造),实现单一职责原则。
- 引入 `TestTemplateBase` 作为纯净的方法挂载容器,避免逻辑代码污染测试用例。
- 优化 YAML 解析流程,将文件扫描、参数化解析与 pytest 方法构建逻辑完全分离。
- 改进装饰器写法,使用更直观的 @ 语法糖处理 Allure 和 pytest.mark.parametrize。
- 增强执行日志,通过类型注解和实例引用记录更详细的运行上下文。
2026-03-06 15:07:22 +08:00

125 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# coding=utf-8
"""
@author: CNWei,ChenWei
@Software: PyCharm
@contact: t6g888@163.com
@file: creator
@date: 2026/3/6 10:40
@desc:
"""
import logging
import allure
import pytest
from pathlib import Path
from core import settings
from core.executor import WorkflowExecutor
from core.session import Session
from commons.exchange import Exchange
from commons.file_processors.yaml_processor import YamlProcessor as FileHandle
from typing import Any, Dict, List, Type, Generator, Tuple
logger = logging.getLogger(__name__)
# 初始化全局组件
session = Session(settings.base_url)
exchanger = Exchange(settings.DATA_DIR / "extract.yaml") # 指向 data/extract.yaml
executor = WorkflowExecutor(session, exchanger)
class TestTemplateBase:
"""
具体的测试用例容器。
此映射类不包含任何逻辑方法,仅用于承载由 Loader 挂载的 test_* 方法。
"""
pass
class CaseDataLoader:
"""
职责 1: 数据加载器
负责与底层存储YAML文件打交道输出标准化的原始数据对象
"""
@staticmethod
def fetch_yaml_cases(cases_dir: str) -> Generator[Tuple[Path, Dict], None, None]:
"""扫描目录并迭代返回 (文件路径, 原始内容)"""
yaml_files = Path(cases_dir).glob("**/test_*.yaml")
for file_path in yaml_files:
yield file_path, FileHandle(file_path)
@staticmethod
def parse_parametrize(raw_data: Dict, default_name: str) -> Tuple[List[str], List[List[Any]], List[str]]:
"""解析参数化结构,返回 (字段名列表, 数据值列表, ID列表)"""
if "parametrize" in raw_data:
fields = raw_data["parametrize"][0]
values = raw_data["parametrize"][1:]
ids = [f"{v[0]}" for v in values]
else:
fields = ["case_data"]
values = [[raw_data]]
ids = [raw_data.get("title", default_name)]
return fields, values, ids
class CaseGenerator:
"""
职责 2: 用例构造工厂
负责将数据转化为 pytest 装饰的方法,并挂载到目标类
"""
@classmethod
def build_and_register(cls, target_cls: Type[TestTemplateBase], cases_dir: str):
# 1. 通过 Loader 获取数据
for file_path, raw_data in CaseDataLoader.fetch_yaml_cases(cases_dir):
# 2. 解析参数化信息
fields, values, ids = CaseDataLoader.parse_parametrize(raw_data, file_path.stem)
# 3. 生成执行函数 (闭包)
dynamic_test_method = cls._create_case_method(raw_data, fields, values, ids)
# 4. 挂载
method_name = f"test_{file_path.stem}"
setattr(target_cls, method_name, dynamic_test_method)
logger.debug(f"Successfully registered: {method_name}")
@staticmethod
def _create_case_method(case_template: Dict, fields: List[str], values: List[Any], ids: List[str]):
"""封装具体的 pytest 执行节点"""
# 预取 Allure 层级信息
epic = case_template.get("epic", settings.allure_epic)
feature = case_template.get("feature", settings.allure_feature)
story = case_template.get("story", settings.allure_story)
@allure.epic(epic)
@allure.feature(feature)
@allure.story(story)
@pytest.mark.parametrize("case_args", values, ids=ids)
def build_actual_case(instance: TestTemplateBase, case_args: List[Any]):
# 数据组装
current_params = dict(zip(fields, case_args))
case_exec_data = {**case_template, **current_params}
case_title = current_params.get("title", "未命名用例")
# 日志记录 (利用 instance 标注来源)
logger.info(f"🚀 [Runner] Class: {instance.__class__.__name__} | Case: {case_title}")
# 执行与断言
allure.dynamic.title(case_title)
executor.perform(case_exec_data)
# 手动链路装饰 (Allure)
# run_actual_case = allure.epic(epic)(run_actual_case)
# run_actual_case = allure.feature(feature)(run_actual_case)
# run_actual_case = allure.story(story)(run_actual_case)
return build_actual_case
if __name__ == '__main__':
# --- 引导执行 ---
CaseGenerator.build_and_register(TestTemplateBase, settings.TEST_CASE_DIR)