chore: v1.5.0 - 提交测试代码、测试报告,更新 tasks.md 状态

This commit is contained in:
2026-06-06 12:52:51 +08:00
parent d8d669bba1
commit ce62d2f353
13 changed files with 2251 additions and 10 deletions

680
Test/test_plan_v1.5.md Normal file
View File

@@ -0,0 +1,680 @@
# PinMAP-to-PinList v1.5.0 — 测试方案
> **版本**: v1.5.0
> **日期**: 2026-06-06
> **设计人**: 测试架构师 (Test Architect)
> **基准**: 修改需求评估 `docs/modification-assessment-v1.5.md`
---
## 1. 变更影响范围
| 编号 | 标题 | 影响文件 | 测试重点 |
|------|------|---------|---------|
| F009 | MAP→List 使用 BallList-Template.xlsx | `main.py` | 模板分离后方向独立,互不干扰 |
| F010 | List→MAP 使用 BallMAP-Template.xlsx | `main.py` | 模板分离后方向独立,互不干扰 |
| F011 | 模板格式提取式应用 | `xlsx_writer.py`, `template_reader.py` | 字体/边框/填充/对齐/列宽/行高正确应用 |
| F012 | PinName 位置确认 + 回归测试 | `test_pinmap.py` | 上/下边 PinName 位置正确,往返一致性 |
---
## 2. 已有测试覆盖分析
### 2.1 单元测试 (`Code/src/test_pinmap.py`) — 9 个用例,全部通过
| 用例 | 覆盖范围 | v1.5 是否受影响 |
|------|---------|----------------|
| `test_4x4_parse` | 4×4 PinMAP 解析 (left/bottom/right/top) | ✅ 已验证 bottom/right Name 位置正确 |
| `test_4x4_validate` | 4×4 PinMAP 验证 | 不受影响 |
| `test_missing_names_warning` | 缺失 Name 警告 | 不受影响 |
| `test_duplicate_numbers` | 重复序号错误 | 不受影响 |
| `test_gap_in_numbers` | 序号不连续错误 | 不受影响 |
| `test_empty_cells` | 空单元格异常 | 不受影响 |
| `test_no_pins` | 无 Pin 数据异常 | 不受影响 |
| `test_12pin_square` | 6×6 12Pin 解析和验证 | 不受影响 |
| `test_f012_pinname_position` | **v1.5 新增** — 5×5 20Pin 往返一致性 | ✅ **v1.5 核心回归测试** |
### 2.2 集成测试 (`Test/run_tests.py`) — 23 个用例,全部通过
| 编号 | 用例 | v1.5 是否受影响 |
|------|------|----------------|
| TC-MAP-001~006 | MAP→List 回归(含错误/警告场景) | ⚠️ F009 影响模板查找逻辑 |
| TC-LM-001~016 | List→MAP 新增(含错误/警告/往返) | ⚠️ F010/F011 影响模板样式应用 |
### 2.3 已有测试缺口v1.5 前)
1. **无模板相关测试** — 现有集成测试仅在 `TC-LM-003` 中使用临时创建的 fake 模板验证 styles.xml 存在,未测试真实的 BallList/BallMAP 模板分离
2. **无样式正确性验证** — 现有测试仅验证 `styles.xml` 文件存在,未验证字体/边框/填充/对齐/列宽/行高的实际内容
3. **无模板降级测试** — 未测试模板不存在/解析失败时的优雅降级行为
4. **无两方向独立模板测试** — 未验证 MAP→List 和 List→MAP 使用不同模板时的隔离性
---
## 3. 完整测试方案
### 3.1 测试分层策略
| 层级 | 位置 | 测试什么 | 执行方式 |
|------|------|---------|---------|
| **Unit** | `Code/src/test_pinmap.py` | 纯逻辑测试(解析/验证/布局/往返),不依赖文件系统 | `python3 Code/src/test_pinmap.py` |
| **Integration** | `Test/run_tests.py` | 文件级测试xlsx 读写/模板加载/端到端),依赖 fixtures/ 和临时文件 | `python3 Test/run_tests.py` |
### 3.2 测试原则
1. **单元测试** (`test_pinmap.py`):纯逻辑测试,不读/写磁盘文件,使用内存中的 cell dict
2. **集成测试** (`run_tests.py`):文件级测试,使用 `Test/fixtures/` 中的真实 .xlsx 文件和临时目录中的动态创建文件
3. **模板 fixture**:需要准备 `BallList-Template.xlsx``BallMAP-Template.xlsx` 两个模板 fixture 文件
---
## 4. F012 — PinName 位置回归测试
### 4.1 状态确认
**代码当前行为**
- `pinmap_layout.py::get_name_cell`: bottom → `(r-1, c)`, top → `(r+1, c)`
- `pinmap_parser.py`: bottom name 读自 `(max_row-1, c)`, top name 读自 `(min_row+1, c)`
- 生成与解析使用**相同约定**,往返一致 ✅
**F012 测试确认**:当前代码已正确,测试已存在并全部通过。
### 4.2 已有回归测试(无需新增)
| 用例 | 位置 | 覆盖 |
|------|------|------|
| `test_f012_pinname_position` | `test_pinmap.py` | 5×5 20Pin 往返:生成 → 逐个验证四条边 Name 位置 → 解析回 PinList → 验证序号一致性 |
### 4.3 测试数据
```
test_pinmap.py 中的 4×4 数据已验证:
bottom: numbers at (6,2)=3, (6,3)=4; names at (5,2)=Pin3, (5,3)=Pin4 → max_row-1 ✅
top: numbers at (1,3)=7, (1,2)=8; names at (2,3)=Pin7, (2,2)=Pin8 → min_row+1 ✅
test_f012 5×5 数据已验证:
bottom: names at (4, c) = rows-1 = max_row-1 ✅
top: names at (2, c) = min_row+1 ✅
left: names at (r, 1) = c+1 ✅
right: names at (r, 4) = c-1 ✅
```
### 4.4 F012 测试结论
**无需新增测试**`test_f012_pinname_position` 已充分覆盖 F012 需求,且当前测试全部通过。保持该测试持续运行即可。
---
## 5. F009 + F010 — 模板分离测试
### 5.1 测试场景矩阵
| 场景 | BallList-Template | BallMAP-Template | MAP→List 预期 | List→MAP 预期 |
|------|-------------------|------------------|-------------|-------------|
| T-F009-01 | ✅ 存在 | — | 加载 BallList应用其样式 | (不涉及) |
| T-F009-02 | ❌ 不存在 | — | 日志提示"未检测到",使用默认样式 | (不涉及) |
| T-F009-03 | 存在但损坏 | — | 日志提示"解析失败",使用默认样式 | (不涉及) |
| T-F010-01 | — | ✅ 存在 | (不涉及) | 加载 BallMAP应用其样式 |
| T-F010-02 | — | ❌ 不存在 | (不涉及) | 日志提示"未检测到",使用默认样式 |
| T-F010-03 | — | 存在但损坏 | (不涉及) | 日志提示"解析失败",使用默认样式 |
| T-F009-04 | ✅ 存在 | ✅ 存在 | 加载 BallList | 加载 BallMAP各自独立互不干扰 |
### 5.2 关键验证点
1. **MAP→List 不使用 BallMAP 模板** — 即使 BallMAP-Template.xlsx 存在MAP→List 也不会加载它
2. **List→MAP 不使用 BallList 模板** — 即使 BallList-Template.xlsx 存在List→MAP 也不会加载它
3. **不加载旧 PinMAP-Template.xlsx** — 即使旧模板存在,两个方向都不加载
### 5.3 单元测试:新增到 `test_pinmap.py`
以下测试**可**新增到 `test_pinmap.py`(纯逻辑,不依赖文件系统):
#### U-F009-F010-001: `_find_balllist_template_path` 和 `_find_ballmap_template_path` 路径生成
```python
def test_template_path_generation():
"""验证两个模板查找函数返回正确的路径格式。"""
import os
from main import _find_balllist_template_path, _find_ballmap_template_path
# 路径应以项目根目录为基础,包含正确的文件名
# 注意:这些函数检查文件是否存在,测试环境可能没有这些文件
# 所以只验证函数可调用且返回类型正确
result1 = _find_balllist_template_path()
result2 = _find_ballmap_template_path()
# 返回值要么是 str 要么是 None
assert result1 is None or isinstance(result1, str)
assert result2 is None or isinstance(result2, str)
# 两者应该是不同路径
if result1 and result2:
assert "BallList" in result1
assert "BallMAP" in result2
assert result1 != result2
print("✓ test_template_path_generation passed")
```
### 5.4 集成测试:新增到 `Test/run_tests.py`
以下测试需要 `Test/fixtures/` 中的模板文件:
**Fixture 准备**
| 文件 | 用途 | 内容要求 |
|------|------|---------|
| `Test/fixtures/BallList-Template.xlsx` | MAP→List 模板 | 至少包含 fonts(Calibri 12pt, bold), borders(thin), column_widths |
| `Test/fixtures/BallMAP-Template.xlsx` | MAP→List 模板 | 至少包含 fonts(Arial 10pt), borders(medium), row_heights |
| `Test/fixtures/template_corrupt.xlsx` | 损坏模板测试 | 一个非 xlsx 的普通文件伪装为 .xlsx |
**新增集成测试用例**
#### TC-v1.5-001: MAP→List 加载 BallList 模板(模板存在)
```
前置: fixtures/BallList-Template.xlsx 存在
步骤:
1. 创建 PinMAP 输入4×4
2. 模拟 run_map_to_list 模板加载流程
3. 验证 read_template_styles 返回非 None
4. 验证返回的 TemplateStyle 包含字体信息
预期: 模板样式成功加载,输出文件包含 styles.xml
```
#### TC-v1.5-002: MAP→List 无模板降级(模板不存在)
```
前置: 删除根目录的 BallList-Template.xlsx
步骤:
1. 创建 PinMAP 输入4×4
2. 调用 _find_balllist_template_path()
3. 验证返回 None
预期: 返回 None输出使用默认样式
```
#### TC-v1.5-003: List→MAP 加载 BallMAP 模板(模板存在)
```
前置: fixtures/BallMAP-Template.xlsx 存在
步骤:
1. 创建 5×5 PinList 输入20 pin
2. 模拟 run_list_to_map 模板加载流程
3. 验证 read_template_styles 返回非 None
4. 生成 PinMAP验证输出包含 styles.xml
预期: 模板样式成功加载,输出文件包含 styles.xml
```
#### TC-v1.5-004: List→MAP 无模板降级(模板不存在)
```
前置: 删除根目录的 BallMAP-Template.xlsx
步骤:
1. 创建 5×5 PinList 输入20 pin
2. 调用 _find_ballmap_template_path()
3. 验证返回 None
预期: 返回 None输出使用默认样式
```
#### TC-v1.5-005: 两个方向独立使用各自模板
```
前置: BallList-Template.xlsx 和 BallMAP-Template.xlsx 都存在
步骤:
1. MAP→List 方向:调用 _find_balllist_template_path(),验证路径包含 "BallList"
2. List→MAP 方向:调用 _find_ballmap_template_path(),验证路径包含 "BallMAP"
3. 验证两个路径不同
预期: 两个方向各自查找各自的模板文件,互不干扰
```
#### TC-v1.5-006: 模板损坏时优雅降级
```
前置: 提供一个损坏的 "template_corrupt.xlsx"
步骤:
1. 调用 read_template_styles("fixtures/template_corrupt.xlsx")
2. 验证返回 None不抛异常
预期: 返回 None调用方可继续使用默认样式
```
---
## 6. F011 — 模板格式提取式应用测试
### 6.1 测试场景
F011 核心要求:模板的**格式信息**(字体/边框/填充/对齐/列宽/行高)正确应用到输出文件,但输出文件的**行列结构**由实际 Pin 数量决定。
### 6.2 单元测试:新增到 `test_pinmap.py`
F011 的样式构建逻辑在 `xlsx_writer.py::StyledXLSXWriter` 中,涉及 XML 字符串生成。以下测试可以在不写磁盘文件的情况下验证 XML 内容:
#### U-F011-001: 无模板时使用默认样式
```python
def test_f011_default_styles_xml():
"""F011: 无模板时 _styles_xml() 返回硬编码默认样式。"""
from xlsx_writer import StyledXLSXWriter
writer = StyledXLSXWriter(style=None)
xml = writer._styles_xml()
# 验证硬编码默认值的存在
assert 'Calibri' in xml, "默认字体应为 Calibri"
assert 'thin' in xml or 'style="thin"' in xml, "默认边框应为 thin"
assert 'center' in xml, "默认对齐应为 center"
assert 'cellXfs count="4"' in xml, "应有 4 个 xf"
print("✓ test_f011_default_styles_xml passed")
```
#### U-F011-002: 有模板时使用模板字体
```python
def test_f011_template_fonts_in_styles_xml():
"""F011: 有模板时 _styles_xml() 使用模板的字体信息(而非硬编码 Calibri"""
from template_reader import TemplateStyle, FontStyle
from xlsx_writer import StyledXLSXWriter
# 构建一个模板样式:微软雅黑 12pt + bold
style = TemplateStyle()
style.fonts = [
FontStyle(name="微软雅黑", size=12.0, bold=False, italic=False, color="FF000000"),
FontStyle(name="微软雅黑", size=12.0, bold=True, italic=False, color="FF000000"),
]
style.fills = []
style.borders = []
style.cell_xfs = [
{'numFmtId': '0', 'fontId': '0', 'fillId': '0', 'borderId': '0', 'xfId': '0'},
]
writer = StyledXLSXWriter(style=style)
xml = writer._styles_xml()
assert '微软雅黑' in xml, f"模板字体名应出现在 styles.xml 中\n{xml[:500]}"
assert '12' in xml or '12.0' in xml, f"模板字号 12pt 应出现在 styles.xml 中"
print("✓ test_f011_template_fonts_in_styles_xml passed")
```
#### U-F011-003: 有模板时使用模板边框
```python
def test_f011_template_borders_in_styles_xml():
"""F011: 有模板时 _styles_xml() 使用模板的边框样式(而非硬编码 thin"""
from template_reader import TemplateStyle, BorderStyle, FontStyle
from xlsx_writer import StyledXLSXWriter
style = TemplateStyle()
style.fonts = [FontStyle(name="Calibri", size=11.0)]
style.borders = [
BorderStyle(top="none", bottom="none", left="none", right="none"),
BorderStyle(top="medium", bottom="medium", left="medium", right="medium"),
]
style.fills = []
style.cell_xfs = [
{'numFmtId': '0', 'fontId': '0', 'fillId': '0', 'borderId': '1', 'xfId': '0',
'applyBorder': '1'},
]
writer = StyledXLSXWriter(style=style)
xml = writer._styles_xml()
# 模板的 medium 边框应该存在(不仅仅是 thin
assert 'medium' in xml, f"模板 medium 边框应出现在 styles.xml 中\n{xml[:800]}"
print("✓ test_f011_template_borders_in_styles_xml passed")
```
#### U-F011-004: 有模板时使用模板填充
```python
def test_f011_template_fills_in_styles_xml():
"""F011: 有模板时 _styles_xml() 使用模板的填充色(而非硬编码 FFF0F0F0"""
from template_reader import TemplateStyle, FillStyle, FontStyle
from xlsx_writer import StyledXLSXWriter
style = TemplateStyle()
style.fonts = [FontStyle(name="Calibri", size=11.0)]
style.borders = []
style.fills = [
FillStyle(pattern_type="none", fg_color=""),
FillStyle(pattern_type="solid", fg_color="FFFF00"), # 黄色
]
style.cell_xfs = [
{'numFmtId': '0', 'fontId': '0', 'fillId': '0', 'borderId': '0', 'xfId': '0'},
{'numFmtId': '0', 'fontId': '0', 'fillId': '1', 'borderId': '0', 'xfId': '0',
'applyFill': '1'},
]
writer = StyledXLSXWriter(style=style)
xml = writer._styles_xml()
assert 'FFFF00' in xml, f"模板黄色填充应出现在 styles.xml 中\n{xml[:800]}"
print("✓ test_f011_template_fills_in_styles_xml passed")
```
#### U-F011-005: 输出行列由实际 Pin 决定(不复制模板行列结构)
```python
def test_f011_output_dims_determined_by_pins():
"""F011: 输出文件的 dim 由实际 Pin 数量决定,不复制模板的行列结构。
即使模板有 100 行列定义,输出仍只包含实际 Pin 数据的行列。
"""
from template_reader import TemplateStyle, FontStyle
from xlsx_writer import StyledXLSXWriter
style = TemplateStyle()
style.fonts = [FontStyle(name="Calibri", size=11.0)]
style.column_widths = {i: 20.0 for i in range(100)} # 模板有 100 列
style.row_heights = {i: 30.0 for i in range(200)} # 模板有 200 行
style.fills = []
style.borders = []
style.cell_xfs = [
{'numFmtId': '0', 'fontId': '0', 'fillId': '0', 'borderId': '0', 'xfId': '0'},
]
# 仅输出 2 行 3 列的数据(模拟 2×2 PinMAP + A1
data = {
'A1': 'QFP-8',
'A2': '1', 'B2': 'Pin1',
'A3': '2', 'B3': 'Pin2',
}
writer = StyledXLSXWriter(style=style)
sheet_xml = writer._sheet_xml(data)
# dim 应该反映实际数据范围A1:C3而非模板的 100 列
assert 'dimension ref="A1:C' in sheet_xml or 'dimension ref="A1:C3"' in sheet_xml, \
f"dim 应由实际数据决定,不应包含模板的 100 列\n{sheet_xml[:500]}"
# 不应出现 row r="201"(模板的第 200 行)
assert 'row r="201"' not in sheet_xml, "不应包含模板的多余行"
print("✓ test_f011_output_dims_determined_by_pins passed")
```
### 6.3 集成测试:新增到 `Test/run_tests.py`
#### TC-v1.5-007: 模板字体应用到输出文件
```
前置: fixtures/BallMAP-Template.xlsx 使用微软雅黑 14pt
步骤:
1. 用该模板生成 5×5 PinMAP 输出
2. 解压输出 xlsx读取 xl/styles.xml
3. 验证 fonts 部分包含 "微软雅黑" 和 size=14
预期: 输出 styles.xml 包含模板字体定义
```
#### TC-v1.5-008: 模板列宽应用到输出文件
```
前置: fixtures/BallList-Template.xlsx 中 A 列宽=25, B 列宽=18
步骤:
1. 用该模板生成 PinList 输出
2. 解压输出 xlsx读取 xl/worksheets/sheet1.xml
3. 验证 cols 元素中 A 列 width=25, B 列 width=18
预期: 输出列宽与模板一致
```
#### TC-v1.5-009: 模板行高应用到输出文件
```
前置: fixtures/BallMAP-Template.xlsx 中行高=25
步骤:
1. 用该模板生成 3×3 PinMAP 输出
2. 解压输出 xlsx读取 xl/worksheets/sheet1.xml
3. 验证 row 元素包含 ht=25
预期: 输出行高与模板一致
```
#### TC-v1.5-010: 两个方向使用不同模板各自的格式
```
前置:
- BallList-Template.xlsx 字体=楷体 12pt
- BallMAP-Template.xlsx 字体=宋体 14pt
步骤:
1. MAP→List 方向:用 BallList 模板生成 PinList验证输出字体=楷体 12pt
2. List→MAP 方向:用 BallMAP 模板生成 PinMAP验证输出字体=宋体 14pt
3. 验证两个输出文件的字体不同
预期: 两个方向的输出各自使用对应模板的字体
```
---
## 7. F009/F010/F011 集成测试
### 7.1 端到端集成测试
#### TC-v1.5-011: 完整往返 + 模板隔离 (MAP→List→MAP)
```
前置: BallList-Template.xlsx 和 BallMAP-Template.xlsx 都存在且格式不同
步骤:
1. 使用 BallList 模板,执行 sample_4x4.xlsx → PinList
2. 验证 PinList 输出包含 BallList 模板的格式特征
3. 使用 BallMAP 模板,执行 PinList → PinMAP
4. 验证 PinMAP 输出包含 BallMAP 模板的格式特征
5. 验证往返后的 PinMAP 与原 PinMAP 数据一致(忽略格式差异)
预期:
- 往返数据完全一致(引脚序号/名称/封装信息)
- 中间 PinList 使用 BallList 模板格式
- 最终 PinMAP 使用 BallMAP 模板格式
```
#### TC-v1.5-012: 无模板完整流程
```
前置: 根目录没有 BallList-Template.xlsx 也没有 BallMAP-Template.xlsx
步骤:
1. 执行 MAP→List 转换
2. 执行 List→MAP 转换
3. 验证两个输出文件都能正常生成且数据正确
4. 验证两个输出文件使用默认样式Calibri 11pt
预期: 无模板情况下正常降级,输出使用默认样式,数据正确
```
---
## 8. 边界与异常测试
### 8.1 模板边界测试
| 编号 | 场景 | 预期行为 | 测试级别 |
|------|------|---------|---------|
| TC-BND-001 | 模板 fonts 为空列表 | 回退到默认字体 | Unit |
| TC-BND-002 | 模板 borders 为空列表 | 回退到默认边框thin | Unit |
| TC-BND-003 | 模板 fills 为空列表 | 回退到默认填充 | Unit |
| TC-BND-004 | 模板 cell_xfs 为空列表 | 回退到默认 cellXfs4 个硬编码 xf | Unit |
| TC-BND-005 | 模板 column_widths 为空 dict | 使用默认列宽 8.0 | Unit |
| TC-BND-006 | 模板 row_heights 为空 dict | 不使用自定义行高(使用 Excel 默认) | Unit |
| TC-BND-007 | 模板字体 color 缺少 FF 前缀 | xlsx_writer 自动补全 | Unit |
| TC-BND-008 | 模板填充 fg_color 缺少 FF 前缀 | xlsx_writer 自动补全 | Unit |
| TC-BND-009 | 模板中缺失 styles.xml | read_template_styles 返回 None | Unit |
| TC-BND-010 | 模板中缺失 sheet1.xml | column_widths/row_heights 为空 | Unit |
### 8.2 边界场景集成测试
#### TC-v1.5-013: 模板只有字体、无边框/填充
```
前置: 准备一个只有 fonts 定义的极简模板
步骤:
1. 加载模板样式
2. 验证 fonts 正确提取
3. 验证 borders/fills 使用默认值
4. 生成的输出文件可正常打开
预期: 字体来自模板,边框/填充使用默认值,文件可正常打开
```
#### TC-v1.5-014: 模板列宽少于输出列数
```
前置: 模板定义 3 列宽,但输出需要 6 列
步骤:
1. 加载模板(定义 A-C 列宽)
2. 生成 5×5(6 列) PinMAP
3. 验证模板定义的列使用模板宽度,额外的列使用默认 8.0
预期: 列宽正确扩展,无异常
```
---
## 9. 测试执行计划
### 9.1 新增单元测试(添加到 `test_pinmap.py`
| 优先级 | 编号 | 测试名 | 简述 |
|--------|------|--------|------|
| P0 | U-F009-F010-001 | `test_template_path_generation` | 验证模板路径格式正确且互不相同 |
| P0 | U-F011-001 | `test_f011_default_styles_xml` | 无模板时使用默认样式 |
| P0 | U-F011-002 | `test_f011_template_fonts_in_styles_xml` | 模板字体应用到 styles.xml |
| P1 | U-F011-003 | `test_f011_template_borders_in_styles_xml` | 模板边框应用到 styles.xml |
| P1 | U-F011-004 | `test_f011_template_fills_in_styles_xml` | 模板填充应用到 styles.xml |
| P0 | U-F011-005 | `test_f011_output_dims_determined_by_pins` | 输出 dim 由 Pin 数决定 |
| P1 | U-BND-001 | `test_template_empty_fonts_fallback` | 空 fonts 回退 |
| P1 | U-BND-007 | `test_template_color_prefix_auto_fix` | FF 前缀补全 |
| P1 | U-BND-009 | `test_template_no_styles_xml` | 缺失 styles.xml 降级 |
### 9.2 新增集成测试(添加到 `Test/run_tests.py`
| 优先级 | 编号 | 测试名 | 简述 | 需要 Fixture |
|--------|------|--------|------|------------|
| P0 | TC-v1.5-001 | MAP→List 加载 BallList 模板 | 模板存在时正确加载 | BallList-Template.xlsx |
| P0 | TC-v1.5-002 | MAP→List 无模板降级 | 模板不存在时优雅降级 | 无 |
| P0 | TC-v1.5-003 | List→MAP 加载 BallMAP 模板 | 模板存在时正确加载 | BallMAP-Template.xlsx |
| P0 | TC-v1.5-004 | List→MAP 无模板降级 | 模板不存在时优雅降级 | 无 |
| P0 | TC-v1.5-005 | 两个方向独立模板 | 各自使用各自的模板 | 两个模板 |
| P1 | TC-v1.5-006 | 模板损坏优雅降级 | 损坏模板不抛异常 | template_corrupt.xlsx |
| P1 | TC-v1.5-007 | 模板字体应用到输出 | 输出文件含模板字体 | 特殊字体模板 |
| P1 | TC-v1.5-008 | 模板列宽应用到输出 | 输出列宽=模板列宽 | 特殊列宽模板 |
| P1 | TC-v1.5-009 | 模板行高应用到输出 | 输出行高=模板行高 | 特殊行高模板 |
| P1 | TC-v1.5-010 | 两个方向各自格式 | 两个方向格式独立 | 两个不同格式模板 |
| P1 | TC-v1.5-011 | 完整往返+模板隔离 | MAP→List→MAP 数据一致 | 两个模板 |
| P1 | TC-v1.5-012 | 无模板完整流程 | 无模板正常降级 | 无 |
| P2 | TC-v1.5-013 | 极简模板 | 只有字体的模板 | 极简模板 |
| P2 | TC-v1.5-014 | 列宽扩展 | 模板列宽少于输出列数 | 窄模板 |
---
## 10. Fixture 准备清单
### 10.1 需要创建的 Fixture 文件
| 文件 | 放置位置 | 用途 | 关键内容 |
|------|---------|------|---------|
| `BallList-Template.xlsx` | `Test/fixtures/` | MAP→List 模板 | 字体=楷体 12pt, A列宽=25, B列宽=18, 边框=thin, 对齐=center |
| `BallMAP-Template.xlsx` | `Test/fixtures/` | List→MAP 模板 | 字体=宋体 14pt, 行高=25, 边框=medium, 填充=淡黄 FFFFFF00 |
| `template_corrupt.xlsx` | `Test/fixtures/` | 损坏模板测试 | 一个无效的 ZIP 文件或文本文件伪装为 .xlsx |
| `template_minimal.xlsx` | `Test/fixtures/` | 极简模板测试 | 只有 1 个 font 定义,无 borders/fills |
| `template_narrow.xlsx` | `Test/fixtures/` | 列宽扩展测试 | 只定义 3 列宽A-C但测试输出 6 列 |
### 10.2 集成测试时的模板放置策略
集成测试需要能临时将模板放到正确位置。建议方案:
**方案 A推荐**:在 `run_tests.py` 中使用 `tempfile.mkdtemp` 创建临时目录,将 fixture 模板复制为 `BallList-Template.xlsx` / `BallMAP-Template.xlsx`,然后通过 `os.chdir` 或修改 `sys.path` 让 main.py 的模板查找逻辑找到它们。
**方案 B**:在 `run_tests.py` 中直接调用底层函数(`read_template_styles`, `_find_balllist_template_path`),不经过 main.py 的路径查找,而是直接传入 fixture 路径。
推荐 **方案 B**(更简洁),因为已有测试也是直接调用底层函数。
### 10.3 Fixture 创建方法
使用 `xlsx_writer.py::write_xlsx_with_style` 创建带特定格式的模板文件:
```python
# 创建 BallList-Template.xlsx
from xlsx_writer import StyledXLSXWriter
from template_reader import TemplateStyle, FontStyle, BorderStyle, FillStyle
style = TemplateStyle()
style.fonts = [
FontStyle(name="楷体", size=12.0, bold=False, color="FF000000"),
FontStyle(name="楷体", size=12.0, bold=True, color="FF000000"),
]
style.borders = [
BorderStyle(top="none", bottom="none", left="none", right="none"),
BorderStyle(top="thin", bottom="thin", left="thin", right="thin"),
]
style.fills = [FillStyle(pattern_type="none")]
style.cell_xfs = [
{'numFmtId': '0', 'fontId': '0', 'fillId': '0', 'borderId': '0', 'xfId': '0'},
{'numFmtId': '0', 'fontId': '0', 'fillId': '0', 'borderId': '1', 'xfId': '0',
'applyBorder': '1', 'hAlign': 'center', 'vAlign': 'center'},
]
style.column_widths = {0: 25.0, 1: 18.0}
style.row_heights = {}
# 创建 PinList 模板数据2 行示例数据)
data = {'A1': '封装信息', 'A2': 'Pin1', 'B2': '1', 'A3': 'Pin2', 'B3': '2'}
writer = StyledXLSXWriter(style)
writer.write(data, "fixtures/BallList-Template.xlsx")
```
---
## 11. 预期工作量
| 阶段 | 内容 | 预估时间 |
|------|------|---------|
| Fixture 准备 | 创建 5 个模板 fixture 文件 | 30 min |
| 单元测试 | 新增约 10 个单元测试到 `test_pinmap.py` | 1 hr |
| 集成测试 | 新增约 14 个集成测试到 `run_tests.py` | 1.5 hr |
| 回归验证 | 运行全部测试确认无回归 | 15 min |
| 文档更新 | 更新测试报告 | 10 min |
| **总计** | | **~3.5 hr** |
---
## 12. 测试通过标准
| 条件 | 要求 |
|------|------|
| 现有 9 个单元测试 | 全部通过 |
| 现有 23 个集成测试 | 全部通过 |
| 新增 ~10 个单元测试 | 全部通过 |
| 新增 ~14 个集成测试 | 全部通过 |
| F012 往返测试 (`test_f012_pinname_position`) | 持续通过 |
| F009/F010 模板分离 | 两个方向使用各自模板,互不干扰 |
| F011 模板格式提取 | 字体/边框/填充/对齐/列宽/行高从模板正确应用 |
| 无模板场景 | 优雅降级使用默认样式,不抛异常 |
| 损坏模板场景 | 优雅降级为 None不抛异常 |
---
## 13. 总结
### 13.1 测试设计决策
| 决策 | 说明 |
|------|------|
| F012 不再新增测试 | `test_f012_pinname_position` 已充分覆盖,代码行为已确认正确 |
| F009/F010 模板分离测试侧重降级 | 核心风险在于模板不存在/解析失败时的行为,而非正常路径 |
| F011 样式测试分两层 | 单元层验证 XML 生成,集成层验证最终文件内容 |
| 单元测试不依赖文件系统 | 使用内存中的 `TemplateStyle` 对象直接构造测试数据 |
| 集成测试使用 fixture 文件 | 由 test-executor 预先创建模板 fixture再运行测试 |
### 13.2 测试覆盖矩阵
```
F012 F009 F010 F011 边界 集成
现有 test_pinmap.py ✅ — — — — —
现有 run_tests.py — — — — — ✅
新增 unit tests — ✅ ✅ ✅ ✅ —
新增 integration tests— ✅ ✅ ✅ ✅ ✅
```
### 13.3 下一步
1. **test-executor** 按本方案执行 Fixture 准备(创建 5 个模板文件)
2. **test-executor** 按优先级 P0 → P1 → P2 实施测试代码
3. 运行完整测试套件验证
4. 生成 v1.5.0 最终测试报告
---
*测试方案结束 — 等待 test-executor 执行*