Files
Video-Tree-TRM5/tests/unit/test_synthesizer.py
T
iomgaa 9eb9b86954 feat(question_gen): AnchorContext + 12 题型-层级映射常量
- AnchorContext frozen dataclass: 锚节点生成上下文(node_id, card_text, frame_paths, subtitle, distractor_texts)
- TaskTypeSpec frozen dataclass: 题型生成规格(level, needs_frames, frame_count, context_fields)
- TASK_TYPE_LEVEL_MAP: 12 种 Video-MME 题型 → 树层级 + 生成规格映射
- 11 项单元测试覆盖:映射完整性、值类型、层级合法性、frozen 不变性

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-09 05:10:58 -04:00

135 lines
4.6 KiB
Python

"""synthesizer 模块单元测试 — AnchorContext + 题型映射常量。"""
from __future__ import annotations
import dataclasses
from app.question_gen.synthesizer import TASK_TYPE_LEVEL_MAP, AnchorContext, TaskTypeSpec
ALL_12_TYPES = [
"Object Recognition",
"Attribute Perception",
"OCR Problems",
"Spatial Reasoning",
"Spatial Perception",
"Action Recognition",
"Action Reasoning",
"Counting Problem",
"Temporal Perception",
"Temporal Reasoning",
"Information Synopsis",
"Object Reasoning",
]
class TestTaskTypeLevelMap:
"""TASK_TYPE_LEVEL_MAP 覆盖性与结构测试。"""
def test_covers_all_12_types(self) -> None:
"""映射表必须覆盖全部 12 种 Video-MME 题型。"""
assert set(TASK_TYPE_LEVEL_MAP.keys()) == set(ALL_12_TYPES)
def test_no_extra_types(self) -> None:
"""映射表不得包含 12 种标准题型之外的条目。"""
assert len(TASK_TYPE_LEVEL_MAP) == 12
def test_all_values_are_task_type_spec(self) -> None:
"""每个映射值必须是 TaskTypeSpec 实例。"""
for task_type, spec in TASK_TYPE_LEVEL_MAP.items():
assert isinstance(spec, TaskTypeSpec), f"{task_type} 映射值类型错误: {type(spec)}"
def test_level_values_valid(self) -> None:
"""每个 spec 的 level 必须是合法层级标识。"""
valid_levels = {"L1", "L2", "L3", "L1-L2"}
for task_type, spec in TASK_TYPE_LEVEL_MAP.items():
assert spec.level in valid_levels, (
f"{task_type} 层级 '{spec.level}' 不在 {valid_levels}"
)
def test_context_fields_non_empty(self) -> None:
"""每个 spec 的 context_fields 至少有一个字段。"""
for task_type, spec in TASK_TYPE_LEVEL_MAP.items():
assert len(spec.context_fields) >= 1, f"{task_type} 的 context_fields 为空"
class TestAnchorContext:
"""AnchorContext 数据类测试。"""
def test_frozen(self) -> None:
"""AnchorContext 是不可变的。"""
ctx = AnchorContext(
node_id="L3_001",
card_text="A person walks into a room",
frame_paths=["/data/frames/001.jpg"],
subtitle="Hello there",
distractor_texts=["A car drives by"],
)
assert ctx.node_id == "L3_001"
assert ctx.card_text == "A person walks into a room"
assert ctx.frame_paths == ["/data/frames/001.jpg"]
assert ctx.subtitle == "Hello there"
assert ctx.distractor_texts == ["A car drives by"]
def test_mutation_raises(self) -> None:
"""frozen dataclass 拒绝赋值修改。"""
ctx = AnchorContext(
node_id="L3_001",
card_text="test",
frame_paths=["a.jpg"],
subtitle="",
distractor_texts=["other node"],
)
try:
ctx.node_id = "L3_002" # type: ignore[misc]
raise AssertionError("应抛出 FrozenInstanceError")
except dataclasses.FrozenInstanceError:
pass
def test_empty_subtitle_allowed(self) -> None:
"""subtitle 可以为空字符串。"""
ctx = AnchorContext(
node_id="L2_010",
card_text="scene card",
frame_paths=[],
subtitle="",
distractor_texts=[],
)
assert ctx.subtitle == ""
def test_multiple_frame_paths(self) -> None:
"""frame_paths 可包含多个路径。"""
paths = ["/data/f1.jpg", "/data/f2.jpg", "/data/f3.jpg"]
ctx = AnchorContext(
node_id="L2_005",
card_text="multi-frame event",
frame_paths=paths,
subtitle="Dialogue line",
distractor_texts=["other1", "other2"],
)
assert len(ctx.frame_paths) == 3
class TestTaskTypeSpec:
"""TaskTypeSpec 数据类测试。"""
def test_frozen(self) -> None:
"""TaskTypeSpec 是不可变的。"""
spec = TaskTypeSpec(
level="L3",
needs_frames=True,
frame_count="1",
context_fields=("frame_summary",),
)
try:
spec.level = "L2" # type: ignore[misc]
raise AssertionError("应抛出 FrozenInstanceError")
except dataclasses.FrozenInstanceError:
pass
def test_context_fields_is_tuple(self) -> None:
"""context_fields 应为 tuple(不可变)。"""
for task_type, spec in TASK_TYPE_LEVEL_MAP.items():
assert isinstance(spec.context_fields, tuple), (
f"{task_type} 的 context_fields 不是 tuple"
)