feat: 新增DDT模式的支持
- 新增 data_loader 数据驱动加载器。 - 新增 test_keyword_sample 测试执行代码
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: conftest
|
@file: conftest
|
||||||
@date: 2026/1/16 10:52
|
@date: 2026/1/16 10:52
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: __init__.py
|
@file: __init__.py
|
||||||
@date: 2026/1/16 10:49
|
@date: 2026/1/16 10:49
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: modules
|
@file: modules
|
||||||
@date: 2026/1/20 11:54
|
@date: 2026/1/20 11:54
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: test
|
@file: test
|
||||||
@date: 2026/1/12 10:21
|
@date: 2026/1/12 10:21
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: settings
|
@file: settings
|
||||||
@date: 2026/1/19 16:54
|
@date: 2026/1/19 16:54
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
11
main.py
11
main.py
@@ -1,3 +1,14 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei,ChenWei
|
||||||
|
@Software: PyCharm
|
||||||
|
@contact: t6g888@163.com
|
||||||
|
@file: main
|
||||||
|
@date: 2026/1/13 16:54
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ dependencies = [
|
|||||||
"appium-python-client>=5.2.4",
|
"appium-python-client>=5.2.4",
|
||||||
"loguru>=0.7.3",
|
"loguru>=0.7.3",
|
||||||
"pytest>=8.3.5",
|
"pytest>=8.3.5",
|
||||||
|
"PyYAML>=6.0.1",
|
||||||
|
"openpyxl>=3.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[tool.uv.index]]
|
[[tool.uv.index]]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: conftest
|
@file: conftest
|
||||||
@date: 2026/1/19 14:08
|
@date: 2026/1/19 14:08
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
82
test_cases/test_keyword_sample.py
Normal file
82
test_cases/test_keyword_sample.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei,ChenWei
|
||||||
|
@Software: PyCharm
|
||||||
|
@contact: t6g888@163.com
|
||||||
|
@file: test_keyword_sample
|
||||||
|
@date: 2026/1/23 17:48
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import logging
|
||||||
|
from core.driver import CoreDriver
|
||||||
|
from utils.data_loader import DataLoader
|
||||||
|
from core.settings import BASE_DIR
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 假设数据文件路径
|
||||||
|
DATA_FILE = BASE_DIR / "test_cases" / "data" / "login_flow.xlsx"
|
||||||
|
|
||||||
|
|
||||||
|
# 或者 "login_flow.yaml" —— 代码不需要改动,只需要改文件名
|
||||||
|
|
||||||
|
class TestKeywordDriven:
|
||||||
|
|
||||||
|
def run_step(self, driver: CoreDriver, step: dict):
|
||||||
|
"""
|
||||||
|
核心执行引擎:反射调用 CoreDriver 的方法
|
||||||
|
"""
|
||||||
|
action_name = step.get("action")
|
||||||
|
if not action_name:
|
||||||
|
return # 跳过无效行
|
||||||
|
|
||||||
|
# 1. 获取 CoreDriver 中对应的方法
|
||||||
|
if not hasattr(driver, action_name):
|
||||||
|
raise ValueError(f"CoreDriver 中未定义方法: {action_name}")
|
||||||
|
|
||||||
|
func = getattr(driver, action_name)
|
||||||
|
|
||||||
|
# 2. 准备参数
|
||||||
|
# 你的 CoreDriver 方法签名通常是 (by, value, [args], timeout)
|
||||||
|
# 我们从 step 字典中提取这些参数
|
||||||
|
kwargs = {}
|
||||||
|
if "by" in step and step["by"]: kwargs["by"] = step["by"]
|
||||||
|
if "value" in step and step["value"]: kwargs["value"] = step["value"]
|
||||||
|
|
||||||
|
# 处理特殊参数,比如 input 方法需要的 text,或者 assert_text 需要的 expected_text
|
||||||
|
# 这里做一个简单的映射,或者在 Excel 表头直接写对应参数名
|
||||||
|
if "args" in step and step["args"]:
|
||||||
|
# 假设 input 的第三个参数是 text,这里简单处理,实际可根据 func.__code__.co_varnames 动态匹配
|
||||||
|
if action_name == "input":
|
||||||
|
kwargs["text"] = str(step["args"]) # 确保 Excel 数字转字符串
|
||||||
|
elif action_name == "assert_text":
|
||||||
|
kwargs["expected_text"] = str(step["args"])
|
||||||
|
elif action_name == "explicit_wait":
|
||||||
|
# 支持你封装的 resolve_wait_method
|
||||||
|
# 此时 method 参数就是 args 列的内容,例如 "toast_visible:成功"
|
||||||
|
kwargs["method"] = step["args"]
|
||||||
|
|
||||||
|
logger.info(f"执行步骤 [{step.get('desc', '无描述')}]: {action_name} {kwargs}")
|
||||||
|
|
||||||
|
# 3. 执行调用
|
||||||
|
func(**kwargs)
|
||||||
|
|
||||||
|
def test_execute_from_file(self, driver):
|
||||||
|
"""
|
||||||
|
主测试入口
|
||||||
|
"""
|
||||||
|
# 1. 加载数据 (自动识别 Excel/YAML)
|
||||||
|
# 注意:实际使用时建议把 load 放在 pytest.mark.parametrize 里
|
||||||
|
# 这里为了演示逻辑写在函数内
|
||||||
|
if not DATA_FILE.exists():
|
||||||
|
pytest.skip("数据文件不存在")
|
||||||
|
|
||||||
|
steps = DataLoader.load(DATA_FILE)
|
||||||
|
|
||||||
|
# 2. 遍历执行
|
||||||
|
for step in steps:
|
||||||
|
self.run_step(driver, step)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: test
|
@file: test
|
||||||
@date: 2026/1/14 10:12
|
@date: 2026/1/14 10:12
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: __init__.py
|
@file: __init__.py
|
||||||
@date: 2026/1/16 09:06
|
@date: 2026/1/16 09:06
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
130
utils/data_loader.py
Normal file
130
utils/data_loader.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei,ChenWei
|
||||||
|
@Software: PyCharm
|
||||||
|
@contact: t6g888@163.com
|
||||||
|
@file: data_loader.py
|
||||||
|
@date: 2026/1/23 13:55
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
# !/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei
|
||||||
|
@desc: 数据驱动加载器 (Adapter Pattern 实现)
|
||||||
|
负责将 YAML, JSON, Excel 统一转换为 Python List[Dict] 格式
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Any, Union
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# 尝试导入 openpyxl,如果未安装则在运行时报错提示
|
||||||
|
try:
|
||||||
|
import openpyxl
|
||||||
|
except ImportError:
|
||||||
|
openpyxl = None
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DataLoader:
|
||||||
|
"""
|
||||||
|
数据加载适配器
|
||||||
|
统一输出格式: List[Dict]
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(file_path: Union[str, Path]) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
入口方法:根据文件后缀分发处理逻辑
|
||||||
|
"""
|
||||||
|
path = Path(file_path)
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(f"测试数据文件未找到: {path}")
|
||||||
|
|
||||||
|
suffix = path.suffix.lower()
|
||||||
|
|
||||||
|
logger.info(f"正在加载测试数据: {path.name}")
|
||||||
|
|
||||||
|
if suffix in ['.yaml', '.yml']:
|
||||||
|
return DataLoader._load_yaml(path)
|
||||||
|
elif suffix == '.json':
|
||||||
|
return DataLoader._load_json(path)
|
||||||
|
elif suffix in ['.xlsx', '.xls']:
|
||||||
|
return DataLoader._load_excel(path)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的文件格式: {suffix}。仅支持 yaml, json, xlsx")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_yaml(path: Path) -> List[Dict]:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
# safe_load 防止代码注入风险
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
# 归一化:如果根节点是字典,转为单元素列表;如果是列表则直接返回
|
||||||
|
return data if isinstance(data, list) else [data]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_json(path: Path) -> List[Dict]:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data if isinstance(data, list) else [data]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_excel(path: Path) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Excel 解析规则:
|
||||||
|
1. 第一行默认为表头 (Keys)
|
||||||
|
2. 后续行为数据 (Values)
|
||||||
|
3. 自动过滤全空行
|
||||||
|
"""
|
||||||
|
if openpyxl is None:
|
||||||
|
raise ImportError("检测到 .xlsx 文件,但未安装 openpyxl。请执行: pip install openpyxl")
|
||||||
|
|
||||||
|
wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
|
||||||
|
# 默认读取第一个 Sheet
|
||||||
|
ws = wb.active
|
||||||
|
|
||||||
|
# 获取所有行
|
||||||
|
rows = list(ws.rows)
|
||||||
|
if not rows:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 解析表头 (第一行)
|
||||||
|
headers = [cell.value for cell in rows[0] if cell.value is not None]
|
||||||
|
|
||||||
|
result = []
|
||||||
|
# 解析数据 (从第二行开始)
|
||||||
|
for row in rows[1:]:
|
||||||
|
# 提取当前行的数据
|
||||||
|
values = [cell.value for cell in row]
|
||||||
|
|
||||||
|
# 如果整行都是 None,跳过
|
||||||
|
if not any(values):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 组装字典: {header: value}
|
||||||
|
row_dict = {}
|
||||||
|
for i, header in enumerate(headers):
|
||||||
|
# 防止越界 (有些行可能数据列数少于表头)
|
||||||
|
val = values[i] if i < len(values) else None
|
||||||
|
# 转换处理:Excel 的数字可能需要转为字符串,视业务需求而定
|
||||||
|
# 这里保持原样,由后续逻辑处理类型
|
||||||
|
row_dict[header] = val
|
||||||
|
|
||||||
|
result.append(row_dict)
|
||||||
|
|
||||||
|
wb.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 调试代码
|
||||||
|
# 假设有一个 test.yaml
|
||||||
|
# print(DataLoader.load("test.yaml"))
|
||||||
|
pass
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: locator_utils
|
@file: locator_utils
|
||||||
@date: 2026/1/20 15:40
|
@date: 2026/1/20 15:40
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
@author: CNWei,ChenWei
|
@author: CNWei,ChenWei
|
||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
@contact: t6g888@163.com,chenwei@zygj.com
|
@contact: t6g888@163.com
|
||||||
@file: logger
|
@file: logger
|
||||||
@date: 2026/1/15 11:30
|
@date: 2026/1/15 11:30
|
||||||
@desc:
|
@desc:
|
||||||
|
|||||||
Reference in New Issue
Block a user