#!/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)