Files
pinmap-to-pinlist/Test/test_plan_v1.5.md

27 KiB
Raw Permalink Blame History

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.xlsxBallMAP-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 路径生成

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: 无模板时使用默认样式

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: 有模板时使用模板字体

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: 有模板时使用模板边框

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: 有模板时使用模板填充

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 决定(不复制模板行列结构)

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 创建带特定格式的模板文件:

# 创建 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 执行