1
0

test: add comprehensive pytest test suite

Add complete test infrastructure for yaml2pptx project with 245+ tests
covering unit, integration, and end-to-end scenarios.

Test structure:
- Unit tests: elements, template system, validators, loaders, utils
- Integration tests: presentation and rendering flows
- E2E tests: CLI commands (convert, check, preview)

Key features:
- PptxFileValidator for Level 2 PPTX validation (file structure,
  element count, content matching, position tolerance)
- Comprehensive fixtures for test data consistency
- Mock-based testing for external dependencies
- Test images generated with PIL/Pillow
- Boundary case coverage for edge scenarios

Dependencies added:
- pytest, pytest-cov, pytest-mock
- pillow (for test image generation)

Documentation updated:
- README.md: test running instructions
- README_DEV.md: test development guide

Co-authored-by: OpenSpec change: add-comprehensive-tests
This commit is contained in:
2026-03-02 23:11:34 +08:00
parent 027a832c9a
commit ab2510a400
56 changed files with 7035 additions and 6 deletions

View File

@@ -0,0 +1,96 @@
## ADDED Requirements
### Requirement: Convert 命令端到端测试
系统 SHALL 提供 convert 命令的端到端测试。
#### Scenario: 基本转换
- **WHEN** 执行 `yaml2pptx.py convert input.yaml output.pptx`
- **THEN** 输出文件成功生成
- **AND** 文件可以打开并包含正确内容
#### Scenario: 自动输出文件名
- **WHEN** 执行 `yaml2pptx.py convert input.yaml`(不指定输出)
- **THEN** 使用输入文件名生成 .pptx 文件
#### Scenario: 使用模板
- **WHEN** 执行转换时指定 --template-dir
- **THEN** 模板被正确加载和应用
#### Scenario: 跳过验证
- **WHEN** 执行转换时指定 --skip-validation
- **THEN** 验证步骤被跳过
#### Scenario: 强制覆盖
- **WHEN** 输出文件已存在并使用 --force
- **THEN** 现有文件被覆盖
#### Scenario: 文件已存在不强制
- **WHEN** 输出文件已存在且不使用 --force
- **THEN** 程序提示文件已存在并退出
#### Scenario: 无效输入文件
- **WHEN** 输入文件不存在
- **THEN** 程序显示错误信息并退出
#### Scenario: 包含所有元素类型的转换
- **WHEN** YAML 包含所有元素类型(文本、图片、形状、表格)
- **THEN** 生成的 PPTX 包含所有元素并正确渲染
### Requirement: Check 命令端到端测试
系统 SHALL 提供 check 命令的端到端测试。
#### Scenario: 验证通过
- **WHEN** 执行 `yaml2pptx.py check valid.yaml`
- **THEN** 显示验证通过消息
- **AND** 退出码为 0
#### Scenario: 验证失败
- **WHEN** 执行 `yaml2pptx.py check invalid.yaml`
- **THEN** 显示错误信息
- **AND** 退出码非 0
#### Scenario: 验证包含警告
- **WHEN** YAML 文件有问题但不阻止转换
- **THEN** 显示警告信息
- **AND** 验证标记为通过
#### Scenario: 使用模板验证
- **WHEN** YAML 使用模板并指定 --template-dir
- **THEN** 模板文件也被验证
#### Scenario: 多错误报告
- **WHEN** YAML 文件包含多个错误
- **THEN** 所有错误被列出
- **AND** 每个错误包含位置信息
### Requirement: Preview 命令端到端测试
系统 SHALL 提供 preview 命令的端到端测试(不启动真实服务器)。
#### Scenario: HTML 生成
- **WHEN** 调用 generate_preview_html() 并传入有效 YAML
- **THEN** 返回完整的 HTML 页面
- **AND** HTML 包含所有幻灯片的渲染
#### Scenario: HTML 包含幻灯片元素
- **WHEN** 生成的预览 HTML
- **THEN** 每张幻灯片包含正确的元素渲染
- **AND** CSS 样式正确应用
#### Scenario: 错误处理
- **WHEN** YAML 文件包含错误
- **THEN** generate_preview_html() 返回错误页面 HTML
- **AND** 错误信息正确显示
#### Scenario: SSE 事件流路由
- **WHEN** 访问 /events 路由
- **THEN** 返回 text/event-stream 内容类型
#### Scenario: 文件变化处理
- **WHEN** YAML 文件被修改
- **THEN** YAMLChangeHandler 检测到变化
- **AND** 将 'reload' 消息放入队列
#### Scenario: Flask 应用创建
- **WHEN** 调用 create_flask_app()
- **THEN** 返回配置好的 Flask 应用
- **AND** 包含 / 和 /events 路由

View File

@@ -0,0 +1,77 @@
## ADDED Requirements
### Requirement: Presentation 类集成测试
系统 SHALL 提供 Presentation 类的集成测试,验证模板加载和幻灯片渲染。
#### Scenario: 幻灯片渲染
- **WHEN** 调用 Presentation.render_slide() 并传入幻灯片数据
- **THEN** 返回渲染后的元素列表
- **AND** 模板变量被正确替换
#### Scenario: 模板缓存
- **WHEN** 多次调用 Presentation.get_template() 并使用相同模板名
- **THEN** 返回缓存的 Template 实例
#### Scenario: 模板变量传递
- **WHEN** 幻灯片使用模板并提供 vars
- **THEN** 变量被正确传递给模板并渲染
### Requirement: 渲染流程集成测试
系统 SHALL 提供完整的 YAML 到 PPTX 渲染流程测试。
#### Scenario: 完整渲染流程
- **WHEN** 从 YAML 文件加载演示文稿并生成 PPTX
- **THEN** PPTX 文件成功生成
- **AND** 文件包含正确数量的幻灯片
- **AND** 每张幻灯片包含正确的元素
#### Scenario: 文本元素渲染
- **WHEN** 渲染包含文本元素的幻灯片
- **THEN** 生成的 PPTX 包含文本框
- **AND** 文本内容、字体、颜色、对齐方式正确
#### Scenario: 图片元素渲染
- **WHEN** 渲染包含图片元素的幻灯片
- **THEN** 生成的 PPTX 包含图片
- **AND** 图片位置和尺寸正确
#### Scenario: 形状元素渲染
- **WHEN** 渲染包含形状元素的幻灯片
- **THEN** 生成的 PPTX 包含形状
- **AND** 形状类型、填充、边框正确
#### Scenario: 表格元素渲染
- **WHEN** 渲染包含表格元素的幻灯片
- **THEN** 生成的 PPTX 包含表格
- **AND** 表格行列数、内容、样式正确
#### Scenario: 背景渲染
- **WHEN** 渲染包含背景的幻灯片
- **THEN** 生成的 PPTX 幻灯片背景正确设置
#### Scenario: 模板幻灯片渲染
- **WHEN** 渲染使用模板的幻灯片
- **THEN** 模板被正确加载和渲染
- **AND** 模板变量被正确替换
### Requirement: 验证流程集成测试
系统 SHALL 提供完整验证流程的集成测试。
#### Scenario: 完整验证流程
- **WHEN** 调用 Validator.validate() 并传入有效 YAML 文件
- **THEN** 返回 valid=True 的 ValidationResult
- **AND** errors 列表为空
#### Scenario: 多错误收集
- **WHEN** YAML 文件包含多个错误
- **THEN** ValidationResult 包含所有发现的错误
- **AND** 每个错误包含正确的位置信息
#### Scenario: 错误和警告分类
- **WHEN** YAML 文件包含错误和警告
- **THEN** ValidationResult 分别将问题分类到 errors 和 warnings
#### Scenario: 验证结果格式化
- **WHEN** 调用 ValidationResult.format_output()
- **THEN** 返回格式化的字符串
- **AND** 包含错误、警告、提示的分级显示

View File

@@ -0,0 +1,76 @@
## ADDED Requirements
### Requirement: PPTX 文件验证工具
系统 SHALL 提供 PPTX 文件验证工具类,支持文件级别、幻灯片级别、元素级别的验证。
#### Scenario: 文件级别验证
- **WHEN** 验证生成的 PPTX 文件
- **THEN** 文件存在且大小大于 0
- **AND** 文件可以被 python-pptx 打开
#### Scenario: 幻灯片数量验证
- **WHEN** 验证 PPTX 文件
- **THEN** 幻灯片数量与预期一致
#### Scenario: 幻灯片尺寸验证
- **WHEN** 验证 16:9 PPTX 文件
- **THEN** 幻灯片宽度为 10 英寸,高度为 5.625 英寸
- **AND** 验证 4:3 PPTX 文件时,高度为 7.5 英寸
#### Scenario: 文本元素验证
- **WHEN** 验证包含文本的幻灯片
- **THEN** 找到文本框元素
- **AND** 文本内容与预期一致
- **AND** 字体大小、颜色、对齐方式正确
- **AND** 位置在合理范围内(允许 0.1 英寸误差)
#### Scenario: 图片元素验证
- **WHEN** 验证包含图片的幻灯片
- **THEN** 找到图片元素
- **AND** 图片尺寸和位置与预期一致
- **AND** 位置在合理范围内
#### Scenario: 形状元素验证
- **WHEN** 验证包含形状的幻灯片
- **THEN** 找到形状元素
- **AND** 形状类型正确
- **AND** 填充颜色和边框样式正确
- **AND** 位置在合理范围内
#### Scenario: 表格元素验证
- **WHEN** 验证包含表格的幻灯片
- **THEN** 找到表格元素
- **AND** 行列数与预期一致
- **AND** 单元格内容与预期一致
- **AND** 表头样式正确应用
#### Scenario: 背景验证
- **WHEN** 验证包含背景色的幻灯片
- **THEN** 幻灯片背景颜色与预期一致
#### Scenario: 元素数量验证
- **WHEN** 验证幻灯片
- **THEN** 元素数量与预期一致
- **AND** 元素类型分布正确
#### Scenario: 位置范围验证
- **WHEN** 验证元素位置
- **THEN** 元素位置在幻灯片范围内
- **AND** 允许 0.1 英寸的容忍误差
### Requirement: 验证辅助函数
系统 SHALL 提供验证辅助函数,简化测试代码。
#### Scenario: 颜色值比较
- **WHEN** 比较 PPTX 颜色与预期颜色
- **THEN** 验证辅助函数正确比较 RGB 值
- **AND** 考虑颜色转换误差
#### Scenario: 位置比较
- **WHEN** 比较元素位置与预期位置
- **THEN** 验证辅助函数考虑容忍度
#### Scenario: 批量验证
- **WHEN** 一次验证多个属性
- **THEN** 验证辅助函数返回所有验证结果
- **AND** 包含详细的失败信息

View File

@@ -0,0 +1,42 @@
## ADDED Requirements
### Requirement: Pytest 测试框架配置
项目 SHALL 使用 pytest 作为测试框架,通过 pyproject.toml 配置测试依赖和运行参数。
#### Scenario: 配置测试依赖
- **WHEN** 开发者在 pyproject.toml 中配置测试依赖
- **THEN** 项目包含 pytest、pytest-cov、pytest-mock 等依赖
#### Scenario: 运行测试
- **WHEN** 开发者执行 `uv run pytest`
- **THEN** 所有测试被发现并执行
### Requirement: 共享 Fixtures
测试框架 SHALL 提供 conftest.py 文件,定义所有测试共享的 fixtures。
#### Scenario: 临时目录 fixture
- **WHEN** 测试使用 `temp_dir` fixture
- **THEN** 系统创建一个临时目录并在测试后自动清理
#### Scenario: 测试 YAML 文件 fixture
- **WHEN** 测试使用 `sample_yaml` fixture
- **THEN** 系统创建一个包含最小可用内容的测试 YAML 文件
#### Scenario: 测试图片 fixture
- **WHEN** 测试使用 `sample_image` fixture
- **THEN** 系统创建一个测试用图片文件
#### Scenario: 测试模板 fixture
- **WHEN** 测试使用 `sample_template` fixture
- **THEN** 系统创建包含测试模板的目录
### Requirement: 临时文件管理
测试生成的临时 PPTX 文件 SHALL 存放在系统临时目录,测试后自动清理。
#### Scenario: 临时 PPTX 输出目录
- **WHEN** 测试生成 PPTX 文件
- **THEN** 文件存放在系统临时目录的 yaml2pptx_tests 子目录下
#### Scenario: 自动清理
- **WHEN** 测试运行完成
- **THEN** 所有生成的 test_*.pptx 文件被删除

View File

@@ -0,0 +1,123 @@
## ADDED Requirements
### Requirement: 元素类单元测试
系统 SHALL 为所有元素类TextElement、ImageElement、ShapeElement、TableElement提供单元测试。
#### Scenario: TextElement 创建验证
- **WHEN** 创建 TextElement 实例
- **THEN** box 参数被验证为包含 4 个数字的列表
- **AND** 无效的 box 会引发 ValueError
#### Scenario: TextElement 颜色验证
- **WHEN** TextElement 的 font.color 格式无效
- **THEN** validate() 返回包含 INVALID_COLOR_FORMAT 错误的 ValidationIssue 列表
#### Scenario: TextElement 字体大小验证
- **WHEN** TextElement 的 font.size 小于 8pt
- **THEN** validate() 返回包含 FONT_TOO_SMALL 警告的 ValidationIssue 列表
#### Scenario: ImageElement 创建验证
- **WHEN** 创建 ImageElement 实例
- **THEN** src 参数必须非空
- **AND** 空的 src 会引发 ValueError
#### Scenario: ShapeElement 类型验证
- **WHEN** ShapeElement 的 shape 类型不支持
- **THEN** validate() 返回包含 INVALID_SHAPE_TYPE 错误的 ValidationIssue 列表
#### Scenario: TableElement 数据验证
- **WHEN** TableElement 的 data 行列数不一致
- **THEN** validate() 返回包含 TABLE_INCONSISTENT_COLUMNS 错误的 ValidationIssue 列表
#### Scenario: 元素工厂函数
- **WHEN** 调用 create_element() 并传入不同 type
- **THEN** 返回对应类型的元素对象
- **AND** 不支持的 type 会引发 ValueError
### Requirement: 模板系统单元测试
系统 SHALL 为模板系统Template 类)提供单元测试。
#### Scenario: 模板初始化验证
- **WHEN** Template 初始化时未指定 templates_dir
- **THEN** 引发 YAMLError
#### Scenario: 模板名称验证
- **WHEN** Template 初始化时模板名称包含路径分隔符
- **THEN** 引发 YAMLError
#### Scenario: 变量解析
- **WHEN** 调用 resolve_value() 并包含变量引用
- **THEN** 返回解析后的值
- **AND** 未定义的变量会引发 YAMLError
#### Scenario: 条件渲染评估
- **WHEN** 调用 evaluate_condition() 并传入条件表达式
- **THEN** 返回正确的布尔值
#### Scenario: 模板渲染
- **WHEN** 调用 render() 并提供变量值
- **THEN** 返回渲染后的元素列表
- **AND** 缺少必需变量会引发 YAMLError
### Requirement: 验证器单元测试
系统 SHALL 为所有验证器提供单元测试。
#### Scenario: 几何验证器 - 元素边界检查
- **WHEN** 元素超出页面边界
- **THEN** GeometryValidator 返回 ELEMENT_OUT_OF_BOUNDS 警告
#### Scenario: 几何验证器 - 完全在页面外
- **WHEN** 元素完全在页面外
- **THEN** GeometryValidator 返回 ELEMENT_COMPLETELY_OUT_OF_BOUNDS 警告
#### Scenario: 资源验证器 - 图片检查
- **WHEN** 图片文件不存在
- **THEN** ResourceValidator 返回 IMAGE_FILE_NOT_FOUND 错误
#### Scenario: 资源验证器 - 模板检查
- **WHEN** 模板文件不存在
- **THEN** ResourceValidator 返回 TEMPLATE_FILE_NOT_FOUND 错误
#### Scenario: 主验证器协调
- **WHEN** Validator.validate() 执行
- **THEN** 调用所有子验证器
- **AND** 按级别分类问题ERROR/WARNING/INFO
### Requirement: YAML 加载器单元测试
系统 SHALL 为 YAML 加载器提供单元测试。
#### Scenario: 文件不存在
- **WHEN** 加载不存在的文件
- **THEN** 引发 YAMLError 并包含 "文件不存在" 消息
#### Scenario: YAML 语法错误
- **WHEN** 加载包含语法错误的 YAML 文件
- **THEN** 引发 YAMLError 并包含错误行号信息
#### Scenario: 演示文稿结构验证
- **WHEN** data 缺少 slides 字段
- **THEN** validate_presentation_yaml() 引发 YAMLError
#### Scenario: 模板结构验证
- **WHEN** 模板 data 缺少 elements 字段
- **THEN** validate_template_yaml() 引发 YAMLError
### Requirement: 工具函数单元测试
系统 SHALL 为工具函数提供单元测试。
#### Scenario: 颜色转换 - 完整格式
- **WHEN** 调用 hex_to_rgb() 并传入 #RRGGBB 格式
- **THEN** 返回正确的 (R, G, B) 元组
#### Scenario: 颜色转换 - 短格式
- **WHEN** 调用 hex_to_rgb() 并传入 #RGB 格式
- **THEN** 返回正确的 (R, G, B) 元组
#### Scenario: 颜色转换 - 无效格式
- **WHEN** 调用 hex_to_rgb() 并传入无效格式
- **THEN** 引发 ValueError
#### Scenario: 颜色格式验证
- **WHEN** 调用 validate_color() 并传入有效颜色
- **THEN** 返回 True
- **AND** 无效颜色返回 False