1
0
Files
PPTX/tests/unit/test_renderers/test_pptx_renderer.py
lanyuanxiaoyao c73bd0fedd fix: 修复测试问题,提升测试通过率
修复内容:
- E2E测试命令执行方式:将 python -m uv run 改为 uv run
- HTML渲染器:添加 & 字符的HTML转义
- Presentation尺寸验证:添加尺寸值类型验证
- PPTX验证器:修复文本框检测兼容性
- 验证结果格式化:修复提示信息显示
- Mock配置:修复表格渲染等测试的Mock配置

测试结果:
- 修复前: 264 通过, 42 失败, 1 错误
- 修复后: 297 通过, 9 失败, 1 错误

剩余9个失败为待实现的功能增强(验证器模板变量验证)
2026-03-03 00:42:39 +08:00

452 lines
15 KiB
Python

"""
PPTX 渲染器单元测试
测试 PptxGenerator 类的初始化和渲染方法
"""
import pytest
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch
from renderers.pptx_renderer import PptxGenerator
from loaders.yaml_loader import YAMLError
from core.elements import TextElement, ImageElement, ShapeElement, TableElement
class TestPptxGeneratorInit:
"""PptxGenerator 初始化测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_init_with_16_9_size(self, mock_prs_class):
"""测试使用 16:9 尺寸初始化"""
mock_prs = Mock()
mock_prs_class.return_value = mock_prs
gen = PptxGenerator(size="16:9")
assert gen.prs == mock_prs
# 验证属性被设置(具体值由 Inches 决定)
assert hasattr(mock_prs, "slide_width")
assert hasattr(mock_prs, "slide_height")
@patch("renderers.pptx_renderer.PptxPresentation")
def test_init_with_4_3_size(self, mock_prs_class):
"""测试使用 4:3 尺寸初始化"""
mock_prs = Mock()
mock_prs_class.return_value = mock_prs
gen = PptxGenerator(size="4:3")
assert gen.prs == mock_prs
assert hasattr(mock_prs, "slide_width")
assert hasattr(mock_prs, "slide_height")
@patch("renderers.pptx_renderer.PptxPresentation")
def test_init_with_default_size(self, mock_prs_class):
"""测试默认尺寸"""
mock_prs = Mock()
mock_prs_class.return_value = mock_prs
gen = PptxGenerator()
assert gen.prs == mock_prs
@patch("renderers.pptx_renderer.PptxPresentation")
def test_init_with_invalid_size_raises_error(self, mock_prs_class):
"""测试无效尺寸引发错误"""
from loaders.yaml_loader import YAMLError
with pytest.raises(YAMLError, match="不支持的尺寸比例"):
PptxGenerator(size="21:9")
class TestAddSlide:
"""add_slide 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_add_slide_creates_slide(self, mock_prs_class):
"""测试添加幻灯片"""
mock_prs = Mock()
mock_layout = Mock()
mock_prs.slide_layouts = [None] * 6 + [mock_layout] + [None]
mock_prs.slides.add_slide.return_value = Mock()
mock_prs_class.return_value = mock_prs
gen = PptxGenerator()
slide_data = {"background": None, "elements": []}
gen.add_slide(slide_data)
# 验证添加了幻灯片
mock_prs.slides.add_slide.assert_called_once_with(mock_layout)
@patch("renderers.pptx_renderer.PptxPresentation")
def test_add_slide_with_background(self, mock_prs_class):
"""测试添加带背景的幻灯片"""
mock_prs = Mock()
mock_slide = Mock()
mock_slide.background = Mock()
mock_slide.background.fill = Mock()
mock_prs.slide_layouts = [None] * 7 + [Mock()]
mock_prs.slides.add_slide.return_value = mock_slide
mock_prs_class.return_value = mock_prs
gen = PptxGenerator()
slide_data = {"background": {"color": "#ffffff"}, "elements": []}
gen.add_slide(slide_data)
# 验证背景被设置
assert (
mock_slide.background.fill.solid.called
or mock_slide.background.fill.fore_color_rgb is not None
)
@patch("renderers.pptx_renderer.PptxPresentation")
def test_add_slide_without_background(self, mock_prs_class):
"""测试添加无背景的幻灯片"""
mock_prs = Mock()
mock_slide = Mock()
mock_prs.slide_layouts = [None] * 7 + [Mock()]
mock_prs.slides.add_slide.return_value = mock_slide
mock_prs_class.return_value = mock_prs
gen = PptxGenerator()
slide_data = {"background": None, "elements": []}
gen.add_slide(slide_data)
# 不应该崩溃
assert True
class TestRenderText:
"""_render_text 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_text_element(self, mock_prs_class):
"""测试渲染文本元素"""
mock_slide = self._setup_mock_slide()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = TextElement(
content="Test Content",
box=[1, 2, 3, 1],
font={"size": 18, "bold": True, "color": "#333333", "align": "center"},
)
gen._render_text(mock_slide, elem)
# 验证添加了文本框
mock_slide.shapes.add_textbox.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_text_with_word_wrap(self, mock_prs_class):
"""测试文本自动换行"""
mock_slide = self._setup_mock_slide()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = TextElement(content="Long text", box=[0, 0, 1, 1], font={})
gen._render_text(mock_slide, elem)
# 验证文本框被创建
mock_slide.shapes.add_textbox.assert_called_once()
def _setup_mock_slide(self):
"""辅助函数:创建 mock slide"""
mock_slide = Mock()
mock_text_frame = Mock()
mock_text_frame.word_wrap = True
mock_paragraph = Mock()
mock_paragraph.font = Mock()
mock_text_frame.paragraphs = [mock_paragraph]
mock_textbox = Mock()
mock_textbox.text_frame = mock_text_frame
mock_slide.shapes.add_textbox.return_value = mock_textbox
return mock_slide
class TestRenderImage:
"""_render_image 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_image_element(self, mock_prs_class, temp_dir, sample_image):
"""测试渲染图片元素"""
mock_slide = Mock()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = ImageElement(box=[1, 1, 4, 3], src=sample_image.name)
gen._render_image(mock_slide, elem, temp_dir)
# 验证添加了图片
mock_slide.shapes.add_picture.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_image_nonexistent_file(self, mock_prs_class):
"""测试不存在的图片文件"""
mock_slide = Mock()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = ImageElement(box=[1, 1, 4, 3], src="nonexistent.png")
with pytest.raises(YAMLError, match="图片文件未找到"):
gen._render_image(mock_slide, elem, None)
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_image_with_relative_path(
self, mock_prs_class, temp_dir, sample_image
):
"""测试相对路径图片"""
mock_slide = Mock()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = ImageElement(box=[1, 1, 4, 3], src=sample_image.name)
gen._render_image(mock_slide, elem, temp_dir)
mock_slide.shapes.add_picture.assert_called_once()
class TestRenderShape:
"""_render_shape 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_rectangle(self, mock_prs_class):
"""测试渲染矩形"""
mock_slide = self._setup_mock_slide_for_shape()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2")
gen._render_shape(mock_slide, elem)
mock_slide.shapes.add_shape.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_ellipse(self, mock_prs_class):
"""测试渲染椭圆"""
mock_slide = self._setup_mock_slide_for_shape()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = ShapeElement(box=[1, 1, 2, 2], shape="ellipse", fill="#e24a4a")
gen._render_shape(mock_slide, elem)
mock_slide.shapes.add_shape.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_shape_with_line(self, mock_prs_class):
"""测试带边框的形状"""
mock_slide = self._setup_mock_slide_for_shape()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = ShapeElement(
box=[1, 1, 2, 1],
shape="rectangle",
fill="#4a90e2",
line={"color": "#000000", "width": 2},
)
gen._render_shape(mock_slide, elem)
mock_slide.shapes.add_shape.assert_called_once()
def _setup_mock_slide_for_shape(self):
"""辅助函数:创建用于形状渲染的 mock slide"""
mock_slide = Mock()
mock_shape = Mock()
mock_shape.fill = Mock()
mock_shape.fill.solid = Mock()
mock_shape.line = Mock()
mock_slide.shapes.add_shape.return_value = mock_shape
return mock_slide
class TestRenderTable:
"""_render_table 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_table(self, mock_prs_class):
"""测试渲染表格"""
mock_slide = self._setup_mock_slide_for_table()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = TableElement(
position=[1, 1],
col_widths=[2, 2, 2],
data=[["A", "B", "C"], ["1", "2", "3"]],
style={"font_size": 14},
)
gen._render_table(mock_slide, elem)
# 验证添加了表格
mock_slide.shapes.add_table.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_table_with_header_style(self, mock_prs_class):
"""测试带表头样式的表格"""
mock_slide = self._setup_mock_slide_for_table()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = TableElement(
position=[1, 1],
col_widths=[2, 2],
data=[["H1", "H2"], ["D1", "D2"]],
style={"font_size": 14, "header_bg": "#4a90e2", "header_color": "#ffffff"},
)
gen._render_table(mock_slide, elem)
mock_slide.shapes.add_table.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_table_col_widths_mismatch(self, mock_prs_class):
"""测试列宽不匹配"""
mock_slide = self._setup_mock_slide_for_table()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
elem = TableElement(
position=[1, 1],
col_widths=[2, 3, 4], # 3 列
data=[["A", "B"]], # 2 列数据
style={},
)
with pytest.raises(YAMLError, match="列宽数量"):
gen._render_table(mock_slide, elem)
def _setup_mock_slide_for_table(self):
"""辅助函数:创建用于表格渲染的 mock slide"""
mock_slide = Mock()
# 设置 columns 属性,支持索引访问
class MockColumns:
def __init__(self, count):
self._cols = [Mock() for _ in range(count)]
def __getitem__(self, i):
return self._cols[i]
mock_table = Mock()
mock_table.columns = MockColumns(3)
# 设置 add_table 返回值(包含 .table 属性)
mock_add_table_result = Mock()
mock_add_table_result.table = mock_table
mock_slide.shapes.add_table.return_value = mock_add_table_result
# 设置 rows
mock_table.rows = [Mock()]
for row in mock_table.rows:
row.cells = [Mock()]
for cell in row.cells:
cell.text_frame = Mock()
cell.text_frame.paragraphs = [Mock()]
cell.text_frame.paragraphs[0].font = Mock()
return mock_slide
class TestSave:
"""save 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_save_creates_directory(self, mock_prs_class, tmp_path):
"""测试保存时创建目录"""
mock_prs = Mock()
mock_prs_class.return_value = mock_prs
gen = PptxGenerator()
output_dir = tmp_path / "subdir" / "nested"
output_path = output_dir / "output.pptx"
gen.save(output_path)
# 验证目录被创建
assert output_dir.exists()
mock_prs.save.assert_called_once()
@patch("renderers.pptx_renderer.PptxPresentation")
def test_save_existing_file(self, mock_prs_class, tmp_path):
"""测试保存已存在的文件"""
mock_prs = Mock()
mock_prs_class.return_value = mock_prs
gen = PptxGenerator()
output_path = tmp_path / "output.pptx"
gen.save(output_path)
mock_prs.save.assert_called_once_with(str(output_path))
class TestRenderBackground:
"""_render_background 方法测试类"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_solid_background(self, mock_prs_class):
"""测试纯色背景"""
mock_slide = Mock()
mock_slide.background = Mock()
mock_slide.background.fill = Mock()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
background = {"color": "#ffffff"}
gen._render_background(mock_slide, background)
# 验证背景被设置
assert mock_slide.background.fill.solid.called
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_no_background(self, mock_prs_class):
"""测试无背景"""
mock_slide = Mock()
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
# 不应该崩溃
gen._render_background(mock_slide, None)