完成初版

This commit is contained in:
2026-06-03 17:33:24 +08:00
parent 8b62b674c0
commit eee7d87eb1
16 changed files with 1345 additions and 0 deletions
View File
+47
View File
@@ -0,0 +1,47 @@
import pytest
from pathlib import Path
from openpyxl import Workbook
@pytest.fixture
def sample_excel_path() -> Path:
"""返回根目录下的 sample.xlsx 路径"""
path = Path(__file__).parent.parent / "sample.xlsx"
if not path.exists():
pytest.skip(f"测试文件 {path} 不存在")
return path
@pytest.fixture
def invalid_excel_path(tmp_path: Path) -> Path:
"""创建缺少 Scenario1 表的无效 Excel 文件"""
wb = Workbook()
wb.save(tmp_path / "invalid.xlsx")
return tmp_path / "invalid.xlsx"
@pytest.fixture
def sample_data_dict() -> dict:
"""返回示例数据字典"""
return {
"Scenario1": {
"headers": ["Column1", "Column2", "Column3"],
"rows": [
["Value1", "Value2", "Value3"],
["Value4", "Value5", "Value6"]
]
}
}
@pytest.fixture
def sample_sheets_dict() -> dict:
"""返回示例工作表字典"""
return {
"Scenario1": {
"A1": "Header1",
"B1": "Header2",
"A2": "Data1",
"B2": "Data2"
}
}
+20
View File
@@ -0,0 +1,20 @@
import pytest
from src.core.base import DataLog
def test_datalog_defaults():
log = DataLog()
assert log.time == 0.0
assert log.value == ""
def test_datalog_with_values():
log = DataLog(time=1.5, value="test")
assert log.time == 1.5
assert log.value == "test"
def test_datalog_equality():
log1 = DataLog(time=1.0, value="a")
log2 = DataLog(time=1.0, value="a")
assert log1 == log2
+290
View File
@@ -0,0 +1,290 @@
import pytest
from pathlib import Path
from openpyxl import Workbook
from src.core.mil_case_excel import (
read_excel_case,
create_excel_case,
__get_template_version,
__analysis_action,
__analysis_case,
__analysis_step,
__init_data_log
)
from src.core.exceptions import ExcelReadError, ExcelFormatError, CaseDataError
from src.core.base import DataLog
@pytest.fixture
def sample_data_dict():
"""返回示例数据字典"""
return {
"signal1": {
"type": "Type1",
"column": 2,
"datalog": [DataLog(0.0, "initial"), DataLog(1.0, "value1")]
},
"signal2": {
"type": "Type2",
"column": 3,
"datalog": [DataLog(0.0, "init2"), DataLog(1.0, "value2")]
}
}
@pytest.fixture
def sample_sheets_dict():
"""返回示例工作表字典"""
return {
"TestSheet": {
"TestCase1": {
"enable": False,
"step": {
"step0": {
"name": "Step1",
"time": 1.0,
"action": {"signal1": "new_value1"}
},
"step1": {
"name": "Step2",
"time": 2.0,
"action": {"signal2": "new_value2"}
}
}
},
"TestCase2": {
"enable": True,
"step": {}
}
}
}
def test_get_template_version():
"""验证获取模板版本号"""
wb = Workbook()
sheet = wb.active
sheet.cell(2, 1, "v1.0.0")
sheet.cell(3, 1, None)
version = __get_template_version(sheet)
assert version == "v1.0.0"
def test_get_template_version_empty():
"""验证空工作表的版本号"""
wb = Workbook()
sheet = wb.active
version = __get_template_version(sheet)
assert version is None
def test_analysis_action_valid(sample_data_dict):
"""验证解析有效操作字符串"""
wb = Workbook()
sheet = wb.create_sheet("TestSheet")
sheet.cell(1, 1, "Test")
action = __analysis_action(sheet, 1, 3, "signal1=newvalue", sample_data_dict, "test.xlsx")
assert "signal1" in action
assert action["signal1"] == "newvalue"
def test_analysis_action_multiple(sample_data_dict):
"""验证解析多个操作"""
wb = Workbook()
sheet = wb.create_sheet("TestSheet")
sheet.cell(1, 1, "Test")
action = __analysis_action(sheet, 1, 3, "signal1=v1; signal2=v2", sample_data_dict, "test.xlsx")
assert "signal1" in action
assert "signal2" in action
assert action["signal1"] == "v1"
assert action["signal2"] == "v2"
def test_analysis_action_invalid_signal(sample_data_dict):
"""验证解析无效信号时抛出异常"""
wb = Workbook()
sheet = wb.create_sheet("TestSheet")
sheet.cell(1, 1, "Test")
with pytest.raises(CaseDataError, match="信号 .* 不存在"):
__analysis_action(sheet, 1, 3, "invalid_signal=value", sample_data_dict, "test.xlsx")
def test_init_data_log(sample_data_dict):
"""验证初始化数据日志"""
data_copy = {k: v.copy() for k, v in sample_data_dict.items()}
for name in data_copy:
data_copy[name]["datalog"] = data_copy[name]["datalog"].copy()
__init_data_log(data_copy)
for name in data_copy:
assert len(data_copy[name]["datalog"]) == 1
assert data_copy[name]["datalog"][0].time == 0.0
def test_analysis_step_with_action(sample_data_dict):
"""验证分析带操作的步骤"""
step_dict = {
"step0": {
"name": "TestStep",
"time": 1.5,
"action": {"signal1": "newvalue"}
}
}
data_copy = {k: v.copy() for k, v in sample_data_dict.items()}
for name in data_copy:
data_copy[name]["datalog"] = data_copy[name]["datalog"].copy()
__analysis_step(step_dict, data_copy)
assert len(data_copy["signal1"]["datalog"]) == 3
assert data_copy["signal1"]["datalog"][-1].time == 1.5
assert data_copy["signal1"]["datalog"][-1].value == "newvalue"
def test_analysis_step_without_action(sample_data_dict):
"""验证分析不带操作的步骤"""
step_dict = {
"step0": {
"name": "TestStep",
"time": 1.0
}
}
data_copy = {k: v.copy() for k, v in sample_data_dict.items()}
for name in data_copy:
data_copy[name]["datalog"] = data_copy[name]["datalog"].copy()
initial_length = len(data_copy["signal1"]["datalog"])
__analysis_step(step_dict, data_copy)
assert len(data_copy["signal1"]["datalog"]) == initial_length + 1
def test_analysis_case(sample_data_dict, sample_sheets_dict):
"""验证用例分析"""
result = __analysis_case(sample_data_dict, sample_sheets_dict)
assert "TestCase1" in result
assert "TestCase2" not in result
def test_analysis_case_skips_enabled_cases(sample_data_dict):
"""验证跳过早启用的用例"""
sheets_dict = {
"TestSheet": {
"EnabledCase": {
"enable": True,
"step": {}
}
}
}
result = __analysis_case(sample_data_dict, sheets_dict)
assert "EnabledCase" not in result
def test_read_excel_case_file_not_found():
"""验证文件不存在时抛出异常"""
with pytest.raises(ExcelReadError, match="不存在"):
read_excel_case("nonexistent.xlsx", {}, False)
def test_read_excel_case_missing_atech_sheet(tmp_path: Path):
"""验证缺少 Atech-Hefei 表时抛出异常"""
wb = Workbook()
wb.create_sheet("OtherSheet")
invalid_path = tmp_path / "invalid.xlsx"
wb.save(invalid_path)
with pytest.raises(ExcelFormatError, match="缺少 Atech-Hefei 表"):
read_excel_case(invalid_path, {}, False)
def test_read_excel_case_missing_version(tmp_path: Path, sample_data_dict):
"""验证缺少版本号时抛出异常"""
wb = Workbook()
wb.create_sheet("Atech-Hefei")
wb.create_sheet("TestSheet")
invalid_path = tmp_path / "no_version.xlsx"
wb.save(invalid_path)
with pytest.raises(ExcelFormatError, match="缺少模板版本号"):
read_excel_case(invalid_path, sample_data_dict, False)
def test_read_excel_case_success(tmp_path: Path, sample_data_dict):
"""验证成功读取用例模板"""
wb = Workbook()
wb.remove(wb.active)
version_sheet = wb.create_sheet("Atech-Hefei")
version_sheet.cell(2, 1, "v1.0.0")
test_sheet = wb.create_sheet("TestSheet")
test_sheet.cell(2, 1, "TestCase1")
test_sheet.cell(2, 2, "未完成")
test_sheet.cell(2, 3, None)
test_sheet.cell(2, 4, "Step1")
test_sheet.cell(2, 5, 1.0)
test_sheet.cell(2, 6, None)
test_sheet.cell(3, 1, "TestCase1")
test_sheet.cell(3, 2, "未完成")
test_sheet.cell(3, 3, None)
test_sheet.cell(3, 4, "Step2")
test_sheet.cell(3, 5, 2.0)
test_sheet.cell(3, 6, None)
valid_path = tmp_path / "valid.xlsx"
wb.save(valid_path)
result = read_excel_case(valid_path, sample_data_dict, False)
assert "TestSheet" in result
assert "TestCase1" in result["TestSheet"]
def test_read_excel_case_missing_title(tmp_path: Path, sample_data_dict):
"""验证缺少标题时抛出异常"""
wb = Workbook()
version_sheet = wb.create_sheet("Atech-Hefei")
version_sheet.cell(2, 1, "v1.0.0")
test_sheet = wb.create_sheet("TestSheet")
test_sheet.cell(2, 1, None)
invalid_path = tmp_path / "missing_title.xlsx"
wb.save(invalid_path)
with pytest.raises(CaseDataError, match="必须有标题名"):
read_excel_case(invalid_path, sample_data_dict, False)
def test_read_excel_case_invalid_time(tmp_path: Path, sample_data_dict):
"""验证时间格式错误时抛出异常"""
wb = Workbook()
wb.remove(wb.active)
version_sheet = wb.create_sheet("Atech-Hefei")
version_sheet.cell(2, 1, "v1.0.0")
test_sheet = wb.create_sheet("TestSheet")
test_sheet.cell(2, 1, "TestCase1")
test_sheet.cell(2, 2, "未完成")
test_sheet.cell(2, 4, "Step1")
test_sheet.cell(2, 5, "invalid_time")
test_sheet.cell(2, 6, None)
invalid_path = tmp_path / "invalid_time.xlsx"
wb.save(invalid_path)
with pytest.raises(CaseDataError, match="时间必须是数字"):
read_excel_case(invalid_path, sample_data_dict, False)
+155
View File
@@ -0,0 +1,155 @@
import pytest
from pathlib import Path
from openpyxl import Workbook
from src.core.mil_data_excel import read_excel_data, get_data_log, ExcelReaderConfig
from src.core.exceptions import ExcelReadError, ExcelFormatError
def test_read_excel_data_returns_required_keys(sample_excel_path: Path):
"""验证返回结果包含必需键"""
result = read_excel_data(sample_excel_path)
assert "wb" in result
assert "sheet" in result
assert "source_row" in result
def test_read_excel_data_contains_signals(sample_excel_path: Path):
"""验证能解析出信号数据"""
result = read_excel_data(sample_excel_path)
signal_keys = [k for k in result.keys() if k not in ("wb", "sheet", "source_row")]
assert len(signal_keys) > 0, "应至少包含一个信号"
for name in signal_keys:
assert "type" in result[name]
assert "column" in result[name]
assert "datalog" in result[name]
def test_read_excel_file_not_found():
"""文件不存在时抛出 ExcelReadError"""
with pytest.raises(ExcelReadError, match="不存在"):
read_excel_data("nonexistent.xlsx")
def test_read_excel_missing_sheet(tmp_path: Path):
"""缺少 Scenario1 表时抛出 ExcelFormatError"""
wb = Workbook()
wb.create_sheet("WrongSheet")
invalid_path = tmp_path / "invalid.xlsx"
wb.save(invalid_path)
with pytest.raises(ExcelFormatError, match="缺少"):
read_excel_data(invalid_path)
def test_read_excel_missing_source_header(tmp_path: Path):
"""缺少 Source: Input 标记时抛出 ExcelFormatError"""
wb = Workbook()
sheet = wb.active
sheet.title = ExcelReaderConfig().sheet_name
sheet.cell(1, 1, "time")
sheet.cell(1, 2, "signal1")
invalid_path = tmp_path / "missing_header.xlsx"
wb.save(invalid_path)
with pytest.raises(ExcelFormatError, match="缺少"):
read_excel_data(invalid_path)
def test_datalog_is_list(sample_excel_path: Path):
"""验证 datalog 是 DataLog 对象列表"""
result = read_excel_data(sample_excel_path)
signal_keys = [k for k in result.keys() if k not in ("wb", "sheet", "source_row")]
assert len(signal_keys) > 0
first_signal = result[signal_keys[0]]
assert len(first_signal["datalog"]) > 0
assert hasattr(first_signal["datalog"][0], "time")
assert hasattr(first_signal["datalog"][0], "value")
def test_get_data_log_empty_sheet():
"""空 sheet 返回空列表"""
wb = Workbook()
sheet = wb.active
datalog = get_data_log(sheet, row=1, column=1)
assert len(datalog) == 0
def test_read_excel_data_return_object(sample_excel_path: Path):
"""验证 return_object=True 时返回 ExcelDataResult 对象"""
from src.core.base import ExcelDataResult, SignalData
result = read_excel_data(sample_excel_path, return_object=True)
assert isinstance(result, ExcelDataResult)
assert result.sheet_name == "Scenario1"
assert result.source_row > 0
assert len(result.signals) > 0
def test_read_excel_data_signal_data_access(sample_excel_path: Path):
"""验证 ExcelDataResult 的信号访问方法"""
from src.core.base import SignalData
result = read_excel_data(sample_excel_path, return_object=True)
signal_names = result.get_signal_names()
assert len(signal_names) > 0
first_signal_name = signal_names[0]
signal = result.get_signal(first_signal_name)
assert isinstance(signal, SignalData)
assert signal.datalog is not None
def test_excel_reader_config_defaults():
"""验证 ExcelReaderConfig 默认值"""
config = ExcelReaderConfig()
assert config.sheet_name == "Scenario1"
assert config.source_header == "Source: Input"
assert config.time_column == 1
assert config.header_row == 1
assert config.type_row == 3
assert config.data_start_row_offset == 1
def test_excel_reader_config_custom():
"""验证 ExcelReaderConfig 自定义值"""
config = ExcelReaderConfig(
sheet_name="CustomSheet",
source_header="CustomHeader",
time_column=2,
header_row=2,
type_row=4,
data_start_row_offset=2
)
assert config.sheet_name == "CustomSheet"
assert config.source_header == "CustomHeader"
assert config.time_column == 2
assert config.header_row == 2
assert config.type_row == 4
assert config.data_start_row_offset == 2
def test_read_excel_with_custom_config(tmp_path: Path):
"""验证使用自定义配置读取 Excel"""
wb = Workbook()
sheet = wb.active
sheet.title = "CustomSheet"
sheet.cell(1, 1, "time")
sheet.cell(1, 2, "signal1")
sheet.cell(2, 1, "CustomHeader")
sheet.cell(2, 2, "CustomHeader")
sheet.cell(3, 1, "Type1")
sheet.cell(4, 1, 0.0)
sheet.cell(4, 2, "value1")
sheet.cell(5, 1, 1.0)
sheet.cell(5, 2, "value2")
custom_path = tmp_path / "custom.xlsx"
wb.save(custom_path)
config = ExcelReaderConfig(
sheet_name="CustomSheet",
source_header="CustomHeader"
)
result = read_excel_data(custom_path, return_object=True, config=config)
assert result.sheet_name == "CustomSheet"
assert "signal1" in result.signals