优化代码层级和命名规范
This commit is contained in:
@@ -19,6 +19,7 @@ def main():
|
|||||||
pass
|
pass
|
||||||
create_excel_case(excel_path,data_dict,case_dict)
|
create_excel_case(excel_path,data_dict,case_dict)
|
||||||
# print(case_dict)
|
# print(case_dict)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .base import DataLog, SignalData, ExcelDataResult
|
from .base import DataLog, SignalData, ExcelDataResult
|
||||||
from .mil_data_excel import read_excel_data, get_data_log, ExcelReaderConfig
|
from .mil_read_data_excel import read_excel_data, get_data_log, ExcelReaderConfig
|
||||||
from .mil_case_excel import create_excel_case, read_excel_case
|
from .mil_read_case_excel import read_excel_case
|
||||||
|
from .mil_create_data_excel import create_excel_case
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
MILSDKError,
|
MILSDKError,
|
||||||
ExcelReadError,
|
ExcelReadError,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def setup_logging(
|
|||||||
logger.handlers.clear()
|
logger.handlers.clear()
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
"%(asctime)s [%(levelname)s] %(name)s - %(message)s",
|
"%(asctime)s [%(levelname)s] %(message)s",
|
||||||
datefmt="%Y-%m-%d %H:%M:%S"
|
datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
"""MIL 测试用例 Excel 生成模块"""
|
|
||||||
import copy
|
|
||||||
import logging
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from .base import DataLog
|
|
||||||
from .exceptions import ExcelReadError, ExcelFormatError, CaseDataError, ExcelWriteError
|
|
||||||
from openpyxl import Workbook, load_workbook
|
|
||||||
from openpyxl.worksheet.worksheet import Worksheet
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def read_excel_case(
|
|
||||||
excel_path: str,
|
|
||||||
data_dict: dict,
|
|
||||||
addTimeEn: bool,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""读取 Excel 用例模板
|
|
||||||
|
|
||||||
Args:
|
|
||||||
excel_path: Excel 模板文件路径
|
|
||||||
data_dict: 信号数据字典
|
|
||||||
addTimeEn: 是否累加时间
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
用例字典
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ExcelReadError: 文件读取失败
|
|
||||||
ExcelFormatError: 格式错误
|
|
||||||
CaseDataError: 用例数据错误
|
|
||||||
"""
|
|
||||||
logger.info(f"开始读取用例模板: {excel_path}")
|
|
||||||
try:
|
|
||||||
wb = load_workbook(excel_path, read_only=True, data_only=True)
|
|
||||||
logger.debug(f"Excel 文件加载成功,Sheet 数量: {len(wb.sheetnames)}")
|
|
||||||
except FileNotFoundError:
|
|
||||||
raise ExcelReadError(f"Excel文件 {excel_path} 不存在")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"读取 Excel 文件失败: {e}")
|
|
||||||
raise ExcelReadError(f"无法读取Excel文件 {excel_path}: {str(e)}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
version = __get_template_version(wb["Atech-Hefei"])
|
|
||||||
if version is None:
|
|
||||||
logger.error("缺少模板版本号")
|
|
||||||
raise ExcelFormatError("Excel格式错误,缺少模板版本号")
|
|
||||||
logger.info(f"用例模板版本号: {version}")
|
|
||||||
del wb["Atech-Hefei"]
|
|
||||||
except KeyError:
|
|
||||||
logger.error(f"缺少 'Atech-Hefei' 表")
|
|
||||||
raise ExcelFormatError(f"Excel文件 {excel_path} 格式错误,缺少 Atech-Hefei 表")
|
|
||||||
|
|
||||||
result_dict = {}
|
|
||||||
sheet_count = 0
|
|
||||||
for sheet in wb.worksheets:
|
|
||||||
sheet_count += 1
|
|
||||||
logger.debug(f"正在读取用例模板表: {sheet.title}")
|
|
||||||
|
|
||||||
result_dict[sheet.title] = {}
|
|
||||||
|
|
||||||
new_head = None
|
|
||||||
old_head = None
|
|
||||||
|
|
||||||
step_id = 0
|
|
||||||
row = 2
|
|
||||||
old_time = 0.0
|
|
||||||
while True:
|
|
||||||
if sheet.cell(row=row, column=1).value is not None:
|
|
||||||
new_head = sheet.cell(row=row, column=1).value
|
|
||||||
if new_head is None:
|
|
||||||
raise CaseDataError(
|
|
||||||
f"Excel文件 {excel_path} 格式错误,{sheet.title} 第 {row} 行第 1 列必须有标题名"
|
|
||||||
)
|
|
||||||
if new_head != old_head:
|
|
||||||
step_id = 0
|
|
||||||
old_time = 0.0
|
|
||||||
old_head = new_head
|
|
||||||
|
|
||||||
result_dict[sheet.title][new_head] = {}
|
|
||||||
result_dict[sheet.title][new_head]["step"] = {}
|
|
||||||
|
|
||||||
if sheet.cell(row=row, column=2).value == '完成测试':
|
|
||||||
result_dict[sheet.title][new_head]["enable"] = True
|
|
||||||
else:
|
|
||||||
result_dict[sheet.title][new_head]["enable"] = False
|
|
||||||
|
|
||||||
step_name = sheet.cell(row, 4).value
|
|
||||||
step_time = sheet.cell(row, 5).value
|
|
||||||
if step_time is None:
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
step_time = float(step_time)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
raise CaseDataError(
|
|
||||||
f"Excel文件 {excel_path} 格式错误,{sheet.title} 第 {row} 行第 5 列时间必须是数字"
|
|
||||||
)
|
|
||||||
|
|
||||||
result_dict[sheet.title][new_head]["step"][f"step{step_id}"] = {}
|
|
||||||
result_dict[sheet.title][new_head]["step"][f"step{step_id}"]["name"] = step_name
|
|
||||||
|
|
||||||
if addTimeEn:
|
|
||||||
old_time += step_time
|
|
||||||
result_dict[sheet.title][new_head]["step"][f"step{step_id}"]["time"] = old_time
|
|
||||||
|
|
||||||
strings = sheet.cell(row, 3).value
|
|
||||||
if strings is not None:
|
|
||||||
result_dict[sheet.title][new_head]["step"][f"step{step_id}"]["action"] = \
|
|
||||||
__analysis_action(sheet, row, 3, strings, data_dict, excel_path)
|
|
||||||
step_id += 1
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
logger.info(f"用例模板读取完成,共 {sheet_count} 个表,{sum(len(v) for v in result_dict.values())} 个用例")
|
|
||||||
return result_dict
|
|
||||||
|
|
||||||
|
|
||||||
def create_excel_case(
|
|
||||||
excel_path: str,
|
|
||||||
data_dict: dict,
|
|
||||||
sheets_dict: dict,
|
|
||||||
) -> None:
|
|
||||||
"""生成测试用例 Excel 文件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
excel_path: 输出目录路径
|
|
||||||
data_dict: 数据字典
|
|
||||||
sheets_dict: 工作表字典
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
CaseDataError: 数据异常
|
|
||||||
ExcelWriteError: 文件写入错误
|
|
||||||
"""
|
|
||||||
logger.info(f"开始生成测试用例,输出目录: {excel_path}")
|
|
||||||
wb = data_dict.get('wb')
|
|
||||||
sheet = data_dict.get('sheet')
|
|
||||||
source_row = data_dict.get('source_row')
|
|
||||||
|
|
||||||
if not isinstance(wb, Workbook) or not isinstance(sheet, Worksheet):
|
|
||||||
logger.error("数据类型错误,Workbook 或 Worksheet 类型不匹配")
|
|
||||||
raise CaseDataError("数据异常,Workbook 或 Worksheet 类型错误")
|
|
||||||
|
|
||||||
data_dict_copy = {k: v for k, v in data_dict.items()
|
|
||||||
if k not in ('wb', 'sheet', 'source_row')}
|
|
||||||
datas_dict = __analysis_case(data_dict_copy, sheets_dict)
|
|
||||||
|
|
||||||
case_count = 0
|
|
||||||
for name in datas_dict.keys():
|
|
||||||
case_count += 1
|
|
||||||
logger.debug(f"正在生成用例: {name}")
|
|
||||||
new_wb = copy.deepcopy(wb)
|
|
||||||
sheet = new_wb["Scenario1"]
|
|
||||||
|
|
||||||
generate_path = f"{excel_path}/{name}.xlsx"
|
|
||||||
__write_excel_data(sheet, datas_dict[name], source_row)
|
|
||||||
new_wb.save(generate_path)
|
|
||||||
|
|
||||||
logger.info(f"测试用例生成完成,共 {case_count} 个用例")
|
|
||||||
|
|
||||||
|
|
||||||
def __write_excel_data(sheet: Worksheet, data_dict: dict, source_row: int) -> None:
|
|
||||||
"""写入 Excel 数据"""
|
|
||||||
for name in data_dict.keys():
|
|
||||||
column = data_dict[name]["column"]
|
|
||||||
datalog = data_dict[name]["datalog"]
|
|
||||||
for index, data in enumerate(datalog):
|
|
||||||
try:
|
|
||||||
value = int(data.value)
|
|
||||||
sheet.cell(row=source_row + index, column=column).data_type = "int"
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
value = data.value
|
|
||||||
sheet.cell(row=source_row + index, column=column).data_type = "str"
|
|
||||||
finally:
|
|
||||||
sheet.cell(row=source_row + index, column=column).value = value
|
|
||||||
if column == 2:
|
|
||||||
sheet.cell(row=source_row + index, column=1).value = data.time
|
|
||||||
sheet.cell(row=source_row + index, column=1).data_type = "float"
|
|
||||||
|
|
||||||
time_column = 2
|
|
||||||
while sheet.cell(1, time_column).value != "time":
|
|
||||||
time_column += 1
|
|
||||||
row = 1
|
|
||||||
while sheet.cell(source_row + row, time_column).value is not None:
|
|
||||||
row += 1
|
|
||||||
stop_time = sheet.cell(source_row + row - 1, time_column).value
|
|
||||||
if stop_time < datalog[-1].time:
|
|
||||||
sheet.cell(source_row + row - 1, time_column).value = datalog[-1].time
|
|
||||||
sheet.cell(source_row + row - 1, time_column).data_type = "float"
|
|
||||||
|
|
||||||
|
|
||||||
def __get_template_version(sheet: Worksheet) -> str | None:
|
|
||||||
"""获取 Excel 模板版本号
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sheet: Excel 工作表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
模板版本号,如果未找到返回 None
|
|
||||||
"""
|
|
||||||
row = 2
|
|
||||||
version = None
|
|
||||||
while sheet.cell(row=row, column=1).value is not None:
|
|
||||||
version = sheet.cell(row=row, column=1).value
|
|
||||||
row += 1
|
|
||||||
return version
|
|
||||||
|
|
||||||
|
|
||||||
def __analysis_action(
|
|
||||||
sheet: Worksheet,
|
|
||||||
row: int,
|
|
||||||
column: int,
|
|
||||||
strings: str,
|
|
||||||
data_dict: dict,
|
|
||||||
excel_path: str
|
|
||||||
) -> dict[str, str]:
|
|
||||||
"""解析操作字符串
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sheet: Excel 工作表
|
|
||||||
row: 行号
|
|
||||||
column: 列号
|
|
||||||
strings: 操作字符串
|
|
||||||
data_dict: 数据字典
|
|
||||||
excel_path: Excel 文件路径
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
操作字典
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
CaseDataError: 信号不存在或格式错误
|
|
||||||
"""
|
|
||||||
action_dict = {}
|
|
||||||
strings = strings.replace(";", ";")
|
|
||||||
strings = strings.replace("\n", "").split(';')
|
|
||||||
for string in strings:
|
|
||||||
if string == '':
|
|
||||||
continue
|
|
||||||
string = string.replace(" ", "")
|
|
||||||
signal_name = string[:string.find('=')]
|
|
||||||
if signal_name.lower() not in [k.strip().lower() for k in data_dict.keys()]:
|
|
||||||
raise CaseDataError(
|
|
||||||
f"{sheet.title} 第 {row} 行第 3 列操作中信号 {signal_name} 不存在"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action_dict[signal_name] = string[string.find('=') + 1:]
|
|
||||||
return action_dict
|
|
||||||
|
|
||||||
|
|
||||||
def __analysis_case(data_dict: dict, sheets_dict: dict) -> dict:
|
|
||||||
"""分析用例字典,将用例中的信号名替换为信号值
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data_dict: 数据字典
|
|
||||||
sheets_dict: 工作表字典
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
处理后的用例字典
|
|
||||||
"""
|
|
||||||
datas_dict = {}
|
|
||||||
for sheet_name in sheets_dict.keys():
|
|
||||||
sheet_dict = sheets_dict[sheet_name]
|
|
||||||
|
|
||||||
for case in sheet_dict.keys():
|
|
||||||
try:
|
|
||||||
if sheet_dict[case]["enable"]:
|
|
||||||
continue
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
datas_dict[case] = copy.deepcopy(data_dict)
|
|
||||||
__init_data_log(datas_dict[case])
|
|
||||||
__analysis_step(sheet_dict[case]["step"], datas_dict[case])
|
|
||||||
return datas_dict
|
|
||||||
|
|
||||||
|
|
||||||
def __analysis_step(step_dict: dict, data_dict: dict) -> None:
|
|
||||||
"""分析步骤字典,将步骤中的信号名替换为信号值
|
|
||||||
|
|
||||||
Args:
|
|
||||||
step_dict: 步骤字典
|
|
||||||
data_dict: 数据字典
|
|
||||||
"""
|
|
||||||
for step_key in step_dict.keys():
|
|
||||||
if "action" in step_dict[step_key].keys():
|
|
||||||
__analysis_data(step_dict[step_key]["action"], data_dict, step_dict[step_key]["time"])
|
|
||||||
else:
|
|
||||||
for name in data_dict.keys():
|
|
||||||
datalog = data_dict[name]["datalog"]
|
|
||||||
datalog.append(DataLog(step_dict[step_key]["time"], datalog[-1].value))
|
|
||||||
|
|
||||||
|
|
||||||
def __analysis_data(action_dict: dict, data_dict: dict, time: float) -> None:
|
|
||||||
"""分析数据字典,将数据字典中的信号名替换为信号值
|
|
||||||
|
|
||||||
Args:
|
|
||||||
action_dict: 操作字典
|
|
||||||
data_dict: 数据字典
|
|
||||||
time: 时间戳
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
CaseDataError: 信号不存在
|
|
||||||
"""
|
|
||||||
data_log_len = None
|
|
||||||
for name in action_dict.keys():
|
|
||||||
matched_key = None
|
|
||||||
for key in data_dict.keys():
|
|
||||||
if name.lower() == key.strip().lower():
|
|
||||||
matched_key = key
|
|
||||||
break
|
|
||||||
if matched_key is None:
|
|
||||||
raise CaseDataError(f"数据字典中不存在信号 {name}")
|
|
||||||
else:
|
|
||||||
datalog = data_dict[matched_key]["datalog"]
|
|
||||||
if datalog[-1].time == time:
|
|
||||||
datalog[-1].value = action_dict[name]
|
|
||||||
data_log_len = len(datalog)
|
|
||||||
else:
|
|
||||||
datalog.append(DataLog(time, action_dict[name]))
|
|
||||||
data_log_len = len(datalog)
|
|
||||||
for name in data_dict.keys():
|
|
||||||
datalog = data_dict[name]["datalog"]
|
|
||||||
if len(datalog) != data_log_len:
|
|
||||||
datalog.append(DataLog(time, datalog[-1].value))
|
|
||||||
|
|
||||||
|
|
||||||
def __init_data_log(data_dict: dict) -> None:
|
|
||||||
"""初始化数据日志
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data_dict: 数据字典
|
|
||||||
"""
|
|
||||||
for name in data_dict.keys():
|
|
||||||
data_dict[name]["datalog"] = data_dict[name]["datalog"][:1]
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
"""MIL 测试用例 Excel 生成模块
|
||||||
|
|
||||||
|
主要功能:
|
||||||
|
- 根据数据和用例模板生成测试用例 Excel 文件
|
||||||
|
- 将用例中的信号名替换为实际信号值
|
||||||
|
|
||||||
|
生成的 Excel 文件遵循与读取相同的格式约定。
|
||||||
|
"""
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .base import DataLog
|
||||||
|
from .exceptions import ExcelReadError, ExcelFormatError, CaseDataError, ExcelWriteError
|
||||||
|
from openpyxl import Workbook, load_workbook
|
||||||
|
from openpyxl.worksheet.worksheet import Worksheet
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def create_excel_case(
|
||||||
|
excel_path: str,
|
||||||
|
data_dict: dict,
|
||||||
|
sheets_dict: dict,
|
||||||
|
) -> None:
|
||||||
|
"""生成测试用例 Excel 文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
excel_path: 输出目录路径
|
||||||
|
data_dict: 数据字典
|
||||||
|
sheets_dict: 工作表字典
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CaseDataError: 数据异常
|
||||||
|
ExcelWriteError: 文件写入错误
|
||||||
|
"""
|
||||||
|
logger.info(f"开始生成测试用例,输出目录: {excel_path}")
|
||||||
|
wb = data_dict.get('wb')
|
||||||
|
sheet = data_dict.get('sheet')
|
||||||
|
source_row = data_dict.get('source_row')
|
||||||
|
|
||||||
|
if not isinstance(wb, Workbook) or not isinstance(sheet, Worksheet):
|
||||||
|
logger.error("数据类型错误,Workbook 或 Worksheet 类型不匹配")
|
||||||
|
raise CaseDataError("数据异常,Workbook 或 Worksheet 类型错误")
|
||||||
|
|
||||||
|
data_dict_copy = {k: v for k, v in data_dict.items()
|
||||||
|
if k not in ('wb', 'sheet', 'source_row')}
|
||||||
|
datas_dict = __analysis_case(data_dict_copy, sheets_dict)
|
||||||
|
|
||||||
|
case_count = 0
|
||||||
|
for name in datas_dict.keys():
|
||||||
|
case_count += 1
|
||||||
|
logger.debug(f"正在生成用例: {name}")
|
||||||
|
new_wb = copy.deepcopy(wb)
|
||||||
|
sheet = new_wb["Scenario1"]
|
||||||
|
|
||||||
|
generate_path = f"{excel_path}/{name}.xlsx"
|
||||||
|
__write_excel_data(sheet, datas_dict[name], source_row)
|
||||||
|
# new_wb.save(generate_path)
|
||||||
|
|
||||||
|
logger.info(f"已生成用例: {name},路径: {generate_path}")
|
||||||
|
|
||||||
|
logger.info(f"测试用例生成完成,共 {case_count} 个用例")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __write_excel_data(sheet: Worksheet, data_dict: dict, source_row: int) -> None:
|
||||||
|
"""写入 Excel 数据"""
|
||||||
|
for name in data_dict.keys():
|
||||||
|
column = data_dict[name]["column"]
|
||||||
|
datalog = data_dict[name]["datalog"]
|
||||||
|
for index, data in enumerate(datalog):
|
||||||
|
try:
|
||||||
|
value = int(data.value)
|
||||||
|
sheet.cell(row=source_row + index, column=column).data_type = "int"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
value = data.value
|
||||||
|
sheet.cell(row=source_row + index, column=column).data_type = "str"
|
||||||
|
finally:
|
||||||
|
sheet.cell(row=source_row + index, column=column).value = value
|
||||||
|
if column == 2:
|
||||||
|
sheet.cell(row=source_row + index, column=1).value = data.time
|
||||||
|
sheet.cell(row=source_row + index, column=1).data_type = "float"
|
||||||
|
|
||||||
|
column = 2
|
||||||
|
while sheet.cell(1, column).value != "time":
|
||||||
|
column += 1
|
||||||
|
row = 1
|
||||||
|
while sheet.cell(source_row + row, column).value is not None:
|
||||||
|
row += 1
|
||||||
|
stop_time = sheet.cell(source_row + row - 1, column).value
|
||||||
|
if stop_time < datalog[-1].time:
|
||||||
|
sheet.cell(source_row + row - 1, column).value = datalog[-1].time
|
||||||
|
sheet.cell(source_row + row - 1, column).data_type = "float"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __analysis_case(data_dict: dict, sheets_dict: dict) -> dict:
|
||||||
|
"""分析用例字典,将用例中的信号名替换为信号值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dict: 数据字典
|
||||||
|
sheets_dict: 工作表字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
处理后的用例字典
|
||||||
|
"""
|
||||||
|
datas_dict = {}
|
||||||
|
for sheet_name in sheets_dict.keys():
|
||||||
|
sheet_dict = sheets_dict[sheet_name]
|
||||||
|
|
||||||
|
for case in sheet_dict.keys():
|
||||||
|
try:
|
||||||
|
if sheet_dict[case]["enable"]:
|
||||||
|
continue
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
datas_dict[case] = copy.deepcopy(data_dict)
|
||||||
|
__init_data_log(datas_dict[case])
|
||||||
|
__analysis_step(sheet_dict[case]["step"], datas_dict[case])
|
||||||
|
return datas_dict
|
||||||
|
|
||||||
|
|
||||||
|
def __analysis_step(step_dict: dict, data_dict: dict) -> None:
|
||||||
|
"""分析步骤字典,将步骤中的信号名替换为信号值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
step_dict: 步骤字典
|
||||||
|
data_dict: 数据字典
|
||||||
|
"""
|
||||||
|
for step_key in step_dict.keys():
|
||||||
|
if "action" in step_dict[step_key].keys():
|
||||||
|
__analysis_data(step_dict[step_key]["action"], data_dict, step_dict[step_key]["time"])
|
||||||
|
else:
|
||||||
|
for name in data_dict.keys():
|
||||||
|
datalog = data_dict[name]["datalog"]
|
||||||
|
datalog.append(DataLog(step_dict[step_key]["time"], datalog[-1].value))
|
||||||
|
|
||||||
|
|
||||||
|
def __analysis_data(action_dict: dict, data_dict: dict, time: float) -> None:
|
||||||
|
"""分析数据字典,将数据字典中的信号名替换为信号值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action_dict: 操作字典
|
||||||
|
data_dict: 数据字典
|
||||||
|
time: 时间戳
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CaseDataError: 信号不存在
|
||||||
|
"""
|
||||||
|
data_log_len = None
|
||||||
|
for name in action_dict.keys():
|
||||||
|
matched_key = None
|
||||||
|
for key in data_dict.keys():
|
||||||
|
if name.lower() == key.strip().lower():
|
||||||
|
matched_key = key
|
||||||
|
break
|
||||||
|
if matched_key is None:
|
||||||
|
raise CaseDataError(f"数据字典中不存在信号 {name}")
|
||||||
|
else:
|
||||||
|
datalog = data_dict[matched_key]["datalog"]
|
||||||
|
if datalog[-1].time == time:
|
||||||
|
datalog[-1].value = action_dict[name]
|
||||||
|
data_log_len = len(datalog)
|
||||||
|
else:
|
||||||
|
datalog.append(DataLog(time, action_dict[name]))
|
||||||
|
data_log_len = len(datalog)
|
||||||
|
for name in data_dict.keys():
|
||||||
|
datalog = data_dict[name]["datalog"]
|
||||||
|
if len(datalog) != data_log_len:
|
||||||
|
datalog.append(DataLog(time, datalog[-1].value))
|
||||||
|
|
||||||
|
|
||||||
|
def __init_data_log(data_dict: dict) -> None:
|
||||||
|
"""初始化数据日志
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dict: 数据字典
|
||||||
|
"""
|
||||||
|
for name in data_dict.keys():
|
||||||
|
data_dict[name]["datalog"] = data_dict[name]["datalog"][:1]
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
"""MIL 测试用例 Excel 读取模块
|
||||||
|
|
||||||
|
主要功能:
|
||||||
|
- 读取 Excel 用例模板
|
||||||
|
- 解析测试步骤和操作
|
||||||
|
|
||||||
|
Excel 模板格式约定:
|
||||||
|
- Sheet 'Atech-Hefei': 存储模板版本号
|
||||||
|
- 其他 Sheet: 存储测试用例数据
|
||||||
|
- 列 1: 用例标题
|
||||||
|
- 列 2: 状态(完成测试/未完成)
|
||||||
|
- 列 3: 操作描述(signal1=value1; signal2=value2)
|
||||||
|
- 列 4: 步骤名称
|
||||||
|
- 列 5: 时间
|
||||||
|
"""
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .base import DataLog
|
||||||
|
from .exceptions import ExcelReadError, ExcelFormatError, CaseDataError, ExcelWriteError
|
||||||
|
from openpyxl import Workbook, load_workbook
|
||||||
|
from openpyxl.worksheet.worksheet import Worksheet
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
COL_INDEX_TITLE = 1
|
||||||
|
COL_INDEX_STATUS = 2
|
||||||
|
COL_INDEX_ACTION = 3
|
||||||
|
COL_INDEX_NAME = 4
|
||||||
|
COL_INDEX_TIME = 5
|
||||||
|
STATUS_COMPLETE = "完成测试"
|
||||||
|
|
||||||
|
|
||||||
|
def read_excel_case(
|
||||||
|
excel_path: str,
|
||||||
|
data_dict: dict,
|
||||||
|
addTimeEn: bool,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""读取 Excel 用例模板
|
||||||
|
|
||||||
|
Args:
|
||||||
|
excel_path: Excel 模板文件路径
|
||||||
|
data_dict: 信号数据字典
|
||||||
|
addTimeEn: 是否累加时间
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用例字典
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExcelReadError: 文件读取失败
|
||||||
|
ExcelFormatError: 格式错误
|
||||||
|
CaseDataError: 用例数据错误
|
||||||
|
"""
|
||||||
|
logger.info(f"开始读取用例模板: {excel_path}")
|
||||||
|
try:
|
||||||
|
wb = load_workbook(excel_path, read_only=True, data_only=True)
|
||||||
|
logger.debug(f"Excel 文件加载成功,Sheet 数量: {len(wb.sheetnames)}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise ExcelReadError(f"Excel文件 {excel_path} 不存在")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"读取 Excel 文件失败: {e}")
|
||||||
|
raise ExcelReadError(f"无法读取Excel文件 {excel_path}: {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
version = __get_template_version(wb["Atech-Hefei"])
|
||||||
|
if version is None:
|
||||||
|
logger.error("缺少模板版本号")
|
||||||
|
raise ExcelFormatError("Excel格式错误,缺少模板版本号")
|
||||||
|
logger.info(f"用例模板版本号: {version}")
|
||||||
|
del wb["Atech-Hefei"]
|
||||||
|
except KeyError:
|
||||||
|
logger.error(f"缺少 'Atech-Hefei' 表")
|
||||||
|
raise ExcelFormatError(f"Excel文件 {excel_path} 格式错误,缺少 Atech-Hefei 表")
|
||||||
|
|
||||||
|
result_dict = {}
|
||||||
|
sheet_count = 0
|
||||||
|
for sheet in wb.worksheets:
|
||||||
|
sheet_count += 1
|
||||||
|
logger.debug(f"正在读取用例模板表: {sheet.title}")
|
||||||
|
|
||||||
|
result_dict[sheet.title] = {}
|
||||||
|
|
||||||
|
new_head = None
|
||||||
|
old_head = None
|
||||||
|
|
||||||
|
step_id = 0
|
||||||
|
row = 2
|
||||||
|
old_time = 0.0
|
||||||
|
while True:
|
||||||
|
if sheet.cell(row=row, column=COL_INDEX_TITLE).value is not None:
|
||||||
|
new_head = sheet.cell(row=row, column=COL_INDEX_TITLE).value
|
||||||
|
if new_head is None:
|
||||||
|
raise CaseDataError(
|
||||||
|
f"Excel文件 {excel_path} 格式错误,{sheet.title} 第 {row} 行第 {COL_INDEX_TITLE} 列必须有标题名"
|
||||||
|
)
|
||||||
|
if new_head != old_head:
|
||||||
|
step_id = 0
|
||||||
|
old_time = 0.0
|
||||||
|
old_head = new_head
|
||||||
|
|
||||||
|
result_dict[sheet.title][new_head] = {}
|
||||||
|
result_dict[sheet.title][new_head]["step"] = {}
|
||||||
|
|
||||||
|
if sheet.cell(row=row, column=COL_INDEX_STATUS).value == STATUS_COMPLETE:
|
||||||
|
result_dict[sheet.title][new_head]["enable"] = True
|
||||||
|
else:
|
||||||
|
result_dict[sheet.title][new_head]["enable"] = False
|
||||||
|
|
||||||
|
step_name = sheet.cell(row, COL_INDEX_NAME).value
|
||||||
|
step_time = sheet.cell(row, COL_INDEX_TIME).value
|
||||||
|
if step_time is None:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
step_time = float(step_time)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise CaseDataError(
|
||||||
|
f"Excel文件 {excel_path} 格式错误,{sheet.title} 第 {row} 行第 {COL_INDEX_TIME} 列时间必须是数字"
|
||||||
|
)
|
||||||
|
|
||||||
|
result_dict[sheet.title][new_head]["step"][f"step{step_id}"] = {}
|
||||||
|
result_dict[sheet.title][new_head]["step"][f"step{step_id}"]["name"] = step_name
|
||||||
|
|
||||||
|
if addTimeEn:
|
||||||
|
old_time += step_time
|
||||||
|
result_dict[sheet.title][new_head]["step"][f"step{step_id}"]["time"] = old_time
|
||||||
|
|
||||||
|
strings = sheet.cell(row, COL_INDEX_ACTION).value
|
||||||
|
if strings is not None:
|
||||||
|
result_dict[sheet.title][new_head]["step"][f"step{step_id}"]["action"] = \
|
||||||
|
__analysis_action(sheet, row, COL_INDEX_ACTION, strings, data_dict, excel_path)
|
||||||
|
step_id += 1
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
logger.info(f"用例模板读取完成,共 {sheet_count} 个表,{sum(len(v) for v in result_dict.values())} 个用例")
|
||||||
|
return result_dict
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __get_template_version(sheet: Worksheet) -> str | None:
|
||||||
|
"""获取 Excel 模板版本号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sheet: Excel 工作表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
模板版本号,如果未找到返回 None
|
||||||
|
"""
|
||||||
|
row = 2
|
||||||
|
version = None
|
||||||
|
while sheet.cell(row=row, column=COL_INDEX_TITLE).value is not None:
|
||||||
|
version = sheet.cell(row=row, column=COL_INDEX_TITLE).value
|
||||||
|
row += 1
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def __analysis_action(
|
||||||
|
sheet: Worksheet,
|
||||||
|
row: int,
|
||||||
|
column: int,
|
||||||
|
strings: str,
|
||||||
|
data_dict: dict,
|
||||||
|
excel_path: str
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""解析操作字符串
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sheet: Excel 工作表
|
||||||
|
row: 行号
|
||||||
|
column: 列号
|
||||||
|
strings: 操作字符串
|
||||||
|
data_dict: 数据字典
|
||||||
|
excel_path: Excel 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
操作字典
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CaseDataError: 信号不存在或格式错误
|
||||||
|
"""
|
||||||
|
action_dict: dict[str, str] = {}
|
||||||
|
strings = strings.replace(";", ";").replace("\n", "")
|
||||||
|
|
||||||
|
for item in strings.split(";"):
|
||||||
|
item = item.strip()
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = item.replace(" ", "")
|
||||||
|
if "=" not in item:
|
||||||
|
continue
|
||||||
|
|
||||||
|
signal_name, signal_value = item.split("=", 1)
|
||||||
|
signal_name = signal_name.strip()
|
||||||
|
|
||||||
|
matched_key: str | None = None
|
||||||
|
for key in data_dict.keys():
|
||||||
|
if signal_name.lower() == key.strip().lower():
|
||||||
|
matched_key = key
|
||||||
|
break
|
||||||
|
|
||||||
|
if matched_key is None:
|
||||||
|
raise CaseDataError(
|
||||||
|
f"{sheet.title} 第 {row} 行第 {column} 列操作中信号 {signal_name} 不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
action_dict[matched_key] = signal_value
|
||||||
|
|
||||||
|
return action_dict
|
||||||
|
|
||||||
@@ -2,14 +2,16 @@ import pytest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
|
|
||||||
from src.core.mil_case_excel import (
|
from src.core.mil_read_case_excel import (
|
||||||
read_excel_case,
|
read_excel_case,
|
||||||
create_excel_case,
|
|
||||||
__get_template_version,
|
__get_template_version,
|
||||||
__analysis_action,
|
__analysis_action,
|
||||||
|
)
|
||||||
|
from src.core.mil_create_data_excel import (
|
||||||
|
create_excel_case,
|
||||||
|
__init_data_log,
|
||||||
__analysis_case,
|
__analysis_case,
|
||||||
__analysis_step,
|
__analysis_step,
|
||||||
__init_data_log
|
|
||||||
)
|
)
|
||||||
from src.core.exceptions import ExcelReadError, ExcelFormatError, CaseDataError
|
from src.core.exceptions import ExcelReadError, ExcelFormatError, CaseDataError
|
||||||
from src.core.base import DataLog
|
from src.core.base import DataLog
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import pytest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
|
|
||||||
from src.core.mil_data_excel import read_excel_data, get_data_log, ExcelReaderConfig
|
from src.core.mil_read_data_excel import read_excel_data, get_data_log, ExcelReaderConfig
|
||||||
from src.core.exceptions import ExcelReadError, ExcelFormatError
|
from src.core.exceptions import ExcelReadError, ExcelFormatError
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user