1
0
Files
PPTX/tests/unit/test_template.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
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
模板系统单元测试
测试 Template 类的初始化、变量解析、条件渲染和模板渲染功能
"""
import pytest
from pathlib import Path
from loaders.yaml_loader import YAMLError
from core.template import Template
# ============= 模板初始化测试 =============
class TestTemplateInit:
"""Template 初始化测试类"""
def test_init_without_template_dir_raises_error(self, temp_dir):
"""测试未指定模板目录会引发错误"""
with pytest.raises(YAMLError, match="未指定模板目录"):
Template("title-slide", templates_dir=None)
def test_init_with_path_separator_in_name_raises_error(self, temp_dir):
"""测试模板名称包含路径分隔符会引发错误"""
with pytest.raises(YAMLError, match="模板名称不能包含路径分隔符"):
Template("../etc/passwd", templates_dir=temp_dir)
def test_init_with_nonexistent_template_raises_error(self, temp_dir):
"""测试模板文件不存在会引发错误"""
with pytest.raises(YAMLError, match="模板文件不存在"):
Template("nonexistent", templates_dir=temp_dir)
def test_init_with_valid_template(self, sample_template):
"""测试使用有效模板初始化"""
template = Template("title-slide", templates_dir=sample_template)
assert template.data is not None
assert "elements" in template.data
assert "vars" in template.data
# ============= 变量解析测试 =============
class TestResolveValue:
"""resolve_value 方法测试类"""
def test_resolve_value_simple_variable(self, sample_template):
"""测试解析简单变量"""
template = Template("title-slide", templates_dir=sample_template)
result = template.resolve_value("{title}", {"title": "My Title"})
assert result == "My Title"
def test_resolve_value_multiple_variables(self, sample_template):
"""测试解析多个变量"""
template = Template("title-slide", templates_dir=sample_template)
result = template.resolve_value(
"{title} - {subtitle}", {"title": "Main", "subtitle": "Sub"}
)
assert result == "Main - Sub"
def test_resolve_value_undefined_variable_raises_error(self, sample_template):
"""测试未定义变量会引发错误"""
template = Template("title-slide", templates_dir=sample_template)
with pytest.raises(YAMLError, match="未定义的变量"):
template.resolve_value("{undefined}", {"title": "Test"})
def test_resolve_value_preserves_non_string(self, sample_template):
"""测试非字符串值保持原样"""
template = Template("title-slide", templates_dir=sample_template)
assert template.resolve_value(123, {}) == 123
assert template.resolve_value(None, {}) is None
assert template.resolve_value(["list"], {}) == ["list"]
def test_resolve_value_converts_to_integer(self, sample_template):
"""测试结果转换为整数"""
template = Template("title-slide", templates_dir=sample_template)
result = template.resolve_value("{value}", {"value": "42"})
assert result == 42
assert isinstance(result, int)
def test_resolve_value_converts_to_float(self, sample_template):
"""测试结果转换为浮点数"""
template = Template("title-slide", templates_dir=sample_template)
result = template.resolve_value("{value}", {"value": "3.14"})
assert result == 3.14
assert isinstance(result, float)
# ============= resolve_element 测试 =============
class TestResolveElement:
"""resolve_element 方法测试类"""
def test_resolve_element_dict(self, sample_template):
"""测试解析字典元素"""
template = Template("title-slide", templates_dir=sample_template)
elem = {"content": "{title}", "box": [1, 2, 3, 4]}
result = template.resolve_element(elem, {"title": "Test"})
assert result["content"] == "Test"
assert result["box"] == [1, 2, 3, 4]
def test_resolve_element_list(self, sample_template):
"""测试解析列表元素"""
template = Template("title-slide", templates_dir=sample_template)
elem = ["{a}", "{b}", "static"]
result = template.resolve_element(elem, {"a": "A", "b": "B"})
assert result == ["A", "B", "static"]
def test_resolve_element_nested_structure(self, sample_template):
"""测试解析嵌套结构"""
template = Template("title-slide", templates_dir=sample_template)
elem = {"font": {"size": "{size}", "color": "#000000"}}
result = template.resolve_element(elem, {"size": "24"})
assert result["font"]["size"] == 24
assert result["font"]["color"] == "#000000"
def test_resolve_element_excludes_visible(self, sample_template):
"""测试 visible 字段被排除"""
template = Template("title-slide", templates_dir=sample_template)
elem = {"content": "test", "visible": "{condition}"}
result = template.resolve_element(elem, {})
assert "visible" not in result
# ============= 条件渲染测试 =============
class TestEvaluateCondition:
"""evaluate_condition 方法测试类"""
def test_evaluate_condition_with_non_empty_variable(self, sample_template):
"""测试非空变量条件为真"""
template = Template("title-slide", templates_dir=sample_template)
result = template.evaluate_condition(
"{subtitle != ''}", {"subtitle": "Test Subtitle"}
)
assert result is True
def test_evaluate_condition_with_empty_variable(self, sample_template):
"""测试空变量条件为假"""
template = Template("title-slide", templates_dir=sample_template)
result = template.evaluate_condition("{subtitle != ''}", {"subtitle": ""})
assert result is False
def test_evaluate_condition_with_missing_variable(self, sample_template):
"""测试缺失变量条件为假"""
template = Template("title-slide", templates_dir=sample_template)
result = template.evaluate_condition("{subtitle != ''}", {})
assert result is False
def test_evaluate_condition_unrecognized_format_returns_true(self, sample_template):
"""测试无法识别的条件格式默认返回 True"""
template = Template("title-slide", templates_dir=sample_template)
result = template.evaluate_condition("some other format", {})
assert result is True
# ============= 模板渲染测试 =============
class TestRender:
"""render 方法测试类"""
def test_render_with_required_variable(self, sample_template):
"""测试渲染包含必需变量的模板"""
template = Template("title-slide", templates_dir=sample_template)
result = template.render({"title": "My Presentation"})
# 由于条件渲染subtitle元素被跳过只返回1个元素
assert len(result) == 1
assert result[0]["content"] == "My Presentation"
def test_render_with_optional_variable(self, sample_template):
"""测试渲染包含可选变量的模板"""
template = Template("title-slide", templates_dir=sample_template)
result = template.render({"title": "Test", "subtitle": "Subtitle"})
assert len(result) == 2 # 两个元素
def test_render_without_optional_variable(self, sample_template):
"""测试不提供可选变量"""
template = Template("title-slide", templates_dir=sample_template)
result = template.render({"title": "Test"})
# subtitle 元素应该被跳过visible 条件)
assert len(result) == 1
def test_render_missing_required_variable_raises_error(self, sample_template):
"""测试缺少必需变量会引发错误"""
template = Template("title-slide", templates_dir=sample_template)
with pytest.raises(YAMLError, match="缺少必需变量"):
template.render({}) # 缺少 title
def test_render_with_default_value(self, temp_dir):
"""测试使用默认值"""
# 创建带默认值的模板
template_content = """
vars:
- name: title
required: true
- name: subtitle
required: false
default: "Default Subtitle"
elements:
- type: text
content: "{title}"
- type: text
content: "{subtitle}"
"""
template_file = temp_dir / "templates" / "default-test.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("default-test", templates_dir=temp_dir / "templates")
result = template.render({"title": "Test"})
assert len(result) == 2
def test_render_filters_elements_by_visible_condition(self, sample_template):
"""测试根据 visible 条件过滤元素"""
template = Template("title-slide", templates_dir=sample_template)
# 有 subtitle - 应该显示两个元素
result_with = template.render({"title": "Test", "subtitle": "Sub"})
assert len(result_with) == 2
# 无 subtitle - 应该只显示一个元素
result_without = template.render({"title": "Test"})
assert len(result_without) == 1
# ============= 边界情况补充测试 =============
class TestTemplateBoundaryCases:
"""模板系统边界情况测试"""
def test_nested_variable_resolution(self, temp_dir):
"""测试嵌套变量解析"""
template_content = """
vars:
- name: title
required: true
- name: full_title
required: false
default: "{title} - Extended"
elements:
- type: text
content: "{full_title}"
box: [0, 0, 1, 1]
font: {}
"""
template_file = temp_dir / "templates" / "nested.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("nested", templates_dir=temp_dir / "templates")
result = template.render({"title": "Main"})
# 默认值中的变量应该被解析
assert result[0]["content"] == "Main - Extended"
def test_variable_with_special_characters(self, temp_dir):
"""测试变量包含特殊字符"""
template_content = """
vars:
- name: title
required: true
elements:
- type: text
content: "{title}"
box: [0, 0, 1, 1]
font: {}
"""
template_file = temp_dir / "templates" / "special.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("special", templates_dir=temp_dir / "templates")
special_values = [
"Test & Data",
"Test <Script>",
'Test "Quotes"',
"Test 'Apostrophe'",
"测试中文",
"Test: colon",
"Test; semi",
]
for value in special_values:
result = template.render({"title": value})
assert result[0]["content"] == value
def test_variable_with_numeric_value(self, temp_dir):
"""测试数字变量值"""
template_content = """
vars:
- name: width
required: true
elements:
- type: text
content: "Width: {width}"
box: [0, 0, 1, 1]
font: {}
"""
template_file = temp_dir / "templates" / "numeric.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("numeric", templates_dir=temp_dir / "templates")
result = template.render({"width": "100"})
# 应该保持为字符串(因为是在 content 中)
assert result[0]["content"] == "Width: 100"
def test_empty_vars_list(self, temp_dir):
"""测试空的 vars 列表"""
template_content = """
vars: []
elements:
- type: text
content: "Static Content"
box: [0, 0, 1, 1]
font: {}
"""
template_file = temp_dir / "templates" / "novars.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("novars", templates_dir=temp_dir / "templates")
result = template.render({})
assert len(result) == 1
def test_multiple_visible_conditions(self, temp_dir):
"""测试多个可见性条件"""
template_content = """
vars:
- name: title
required: true
- name: subtitle
required: false
default: ""
- name: footer
required: false
default: ""
elements:
- type: text
content: "{title}"
box: [0, 0, 1, 1]
font: {}
- type: text
content: "{subtitle}"
visible: "{subtitle != ''}"
box: [0, 1, 1, 1]
font: {}
- type: text
content: "{footer}"
visible: "{footer != ''}"
box: [0, 2, 1, 1]
font: {}
"""
template_file = temp_dir / "templates" / "multi-condition.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("multi-condition", templates_dir=temp_dir / "templates")
# 只有 title
result1 = template.render({"title": "Test"})
assert len(result1) == 1
# 有 subtitle 和 footer
result2 = template.render(
{"title": "Test", "subtitle": "Sub", "footer": "Foot"}
)
assert len(result2) == 3
def test_variable_in_position(self, temp_dir):
"""测试变量在位置中使用"""
template_content = """
vars:
- name: x_pos
required: true
elements:
- type: text
content: "Test"
box: ["{x_pos}", 1, 1, 1]
font: {}
"""
template_file = temp_dir / "templates" / "pos-var.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("pos-var", templates_dir=temp_dir / "templates")
result = template.render({"x_pos": "2"})
# 变量应该被解析为数字
assert result[0]["box"][0] == 2
def test_empty_template_elements(self, temp_dir):
"""测试空元素列表的模板"""
template_content = """
vars: []
elements: []
"""
template_file = temp_dir / "templates" / "empty.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("empty", templates_dir=temp_dir / "templates")
result = template.render({})
assert result == []
def test_variable_replacement_in_font(self, temp_dir):
"""测试字体属性中的变量替换"""
template_content = """
vars:
- name: font_size
required: true
- name: text_color
required: true
elements:
- type: text
content: "Styled Text"
box: [0, 0, 1, 1]
font:
size: "{font_size}"
color: "{text_color}"
bold: true
"""
template_file = temp_dir / "templates" / "font-vars.yaml"
template_file.parent.mkdir(exist_ok=True)
template_file.write_text(template_content)
template = Template("font-vars", templates_dir=temp_dir / "templates")
result = template.render({"font_size": "24", "text_color": "#ff0000"})
assert result[0]["font"]["size"] == 24
assert result[0]["font"]["color"] == "#ff0000"
assert result[0]["font"]["color"] == "#ff0000"