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

340
tests/conftest.py Normal file
View File

@@ -0,0 +1,340 @@
"""
pytest 配置文件 - 共享 fixtures
"""
import sys
from pathlib import Path
from PIL import Image
import pytest
# 添加项目根目录到 sys.path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
# ============= 基础 Fixtures =============
@pytest.fixture
def temp_dir(tmp_path):
"""临时目录 fixture使用 pytest 内置 tmp_path"""
return tmp_path
@pytest.fixture
def project_root_dir():
"""项目根目录"""
return Path(__file__).parent.parent
# ============= YAML 文件 Fixtures =============
MINIMAL_YAML = """metadata:
size: 16:9
slides:
- background:
color: "#ffffff"
elements:
- type: text
box: [1, 1, 8, 1]
content: "Hello, World!"
font:
size: 44
bold: true
color: "#333333"
align: center
"""
@pytest.fixture
def sample_yaml(temp_dir):
"""创建最小测试 YAML 文件"""
yaml_path = temp_dir / "test.yaml"
yaml_path.write_text(MINIMAL_YAML, encoding='utf-8')
return yaml_path
# ============= 图片 Fixtures =============
@pytest.fixture
def sample_image(temp_dir):
"""创建测试图片文件(使用 Pillow 生成简单的 PNG"""
img_path = temp_dir / "test_image.png"
# 创建一个简单的红色图片
img = Image.new('RGB', (100, 100), color='red')
img.save(img_path, 'PNG')
return img_path
# ============= 模板 Fixtures =============
TEMPLATE_YAML = """vars:
- name: title
required: true
- name: subtitle
required: false
default: ""
elements:
- type: text
box: [1, 2, 8, 1]
content: "{title}"
font:
size: 44
bold: true
align: center
- type: text
box: [1, 3.5, 8, 0.5]
content: "{subtitle}"
visible: "{subtitle != ''}"
font:
size: 24
align: center
"""
@pytest.fixture
def sample_template(temp_dir):
"""创建测试模板目录和文件"""
template_dir = temp_dir / "templates"
template_dir.mkdir()
(template_dir / "title-slide.yaml").write_text(TEMPLATE_YAML, encoding='utf-8')
return template_dir
# ============= PPTX 验证 Fixture =============
@pytest.fixture
def pptx_validator():
"""PPTX 文件验证器实例"""
from tests.conftest_pptx import PptxFileValidator
return PptxFileValidator()
# ============= 测试数据目录 Fixture =============
@pytest.fixture
def fixtures_dir():
"""测试数据目录路径"""
return Path(__file__).parent / "fixtures"
# ============= 额外的边界情况 Fixtures =============
@pytest.fixture
def edge_case_yaml_files(fixtures_dir):
"""所有边界情况 YAML 文件的路径"""
edge_cases_dir = fixtures_dir / "yaml_samples" / "edge_cases"
if edge_cases_dir.exists():
return list(edge_cases_dir.glob("*.yaml"))
return []
@pytest.fixture(params=["16:9", "4:3"])
def slide_size(request):
"""参数化的幻灯片尺寸"""
return request.param
@pytest.fixture
def complex_template(temp_dir):
"""创建复杂模板(包含多个变量和条件)"""
template_content = """
vars:
- name: title
required: true
- name: subtitle
required: false
default: ""
- name: author
required: false
default: ""
- name: date
required: false
default: ""
elements:
- type: shape
box: [0, 0, 10, 5.625]
shape: rectangle
fill: "#2c3e50"
- type: text
box: [1, 1.5, 8, 1]
content: "{title}"
font:
size: 44
bold: true
color: "#ffffff"
align: center
- type: text
box: [1, 2.8, 8, 0.6]
content: "{subtitle}"
visible: "{subtitle != ''}"
font:
size: 24
color: "#ecf0f1"
align: center
- type: text
box: [1, 4, 8, 0.5]
content: "{author}"
visible: "{author != ''}"
font:
size: 18
color: "#bdc3c7"
align: center
- type: text
box: [1, 4.8, 8, 0.4]
content: "{date}"
visible: "{date != ''}"
font:
size: 14
color: "#95a5a6"
align: center
"""
template_dir = temp_dir / "templates"
template_dir.mkdir()
(template_dir / "complex-slide.yaml").write_text(template_content)
return template_dir
@pytest.fixture
def yaml_with_all_elements(temp_dir):
"""创建包含所有元素类型的 YAML"""
yaml_content = """
metadata:
size: 16:9
slides:
- background:
color: "#ffffff"
elements:
- type: text
box: [1, 1, 3, 0.5]
content: "Text Element"
font:
size: 24
color: "#333333"
align: left
- type: text
box: [1, 1.8, 3, 0.5]
content: "Center Text"
font:
size: 20
color: "#666666"
align: center
- type: text
box: [1, 2.6, 3, 0.5]
content: "Right Text"
font:
size: 18
color: "#999999"
align: right
- type: shape
box: [5, 1, 2, 1]
shape: rectangle
fill: "#4a90e2"
line:
color: "#000000"
width: 1
- type: shape
box: [5, 2.2, 2, 2]
shape: ellipse
fill: "#e24a4a"
line:
color: "#ffffff"
width: 2
- type: shape
box: [5, 4.5, 2, 1]
shape: rounded_rectangle
fill: "#4ae290"
line:
color: "#333333"
width: 1
- type: table
position: [1, 3.5]
col_widths: [2, 2, 2]
data:
- ["Header 1", "Header 2", "Header 3"]
- ["Data 1", "Data 2", "Data 3"]
- ["Data 4", "Data 5", "Data 6"]
style:
font_size: 12
header_bg: "#4a90e2"
header_color: "#ffffff"
"""
yaml_path = temp_dir / "all_elements.yaml"
yaml_path.write_text(yaml_content)
return yaml_path
@pytest.fixture
def invalid_yaml_samples(fixtures_dir):
"""所有无效 YAML 样本的路径"""
invalid_dir = fixtures_dir / "yaml_samples" / "invalid"
if invalid_dir.exists():
return list(invalid_dir.glob("*.yaml"))
return []
@pytest.fixture
def multiple_slides_yaml(temp_dir):
"""创建多张幻灯片的 YAML"""
yaml_content = """
metadata:
size: 16:9
slides:
# 第一张:标题页
- background:
color: "#4a90e2"
elements:
- type: text
box: [1, 2, 8, 1]
content: "Title Slide"
font:
size: 48
bold: true
color: "#ffffff"
align: center
# 第二张:内容页
- background:
color: "#ffffff"
elements:
- type: text
box: [1, 1, 8, 1]
content: "Content Slide 1"
font:
size: 32
color: "#333333"
# 第三张:内容页
- background:
color: "#ffffff"
elements:
- type: text
box: [1, 1, 8, 1]
content: "Content Slide 2"
font:
size: 32
color: "#333333"
- type: shape
box: [3, 2.5, 4, 2]
shape: rectangle
fill: "#e74c3c"
"""
yaml_path = temp_dir / "multiple_slides.yaml"
yaml_path.write_text(yaml_content)
return yaml_path