From 4765d83c8b7bb31237203b9504440d87703209b7 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Tue, 28 Jul 2026 10:34:58 +0800 Subject: [PATCH] test(wave142): add transition_presets unit tests (+65) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TransitionPreset 数据类: 属性、默认值、frozen不可变、相等性 - 预设库完整性: 数量、ID唯一、必填字段、时长范围、分类验证 - get_transition_preset: 存在/不存在、随机、空串、大小写、同实例 - list_transition_presets: 无筛选、按分类、关键词搜索(name/desc/tags)、大小写不敏感、组合筛选、顺序保持 - get_default_transition: 默认值、时长、分类、同实例 - 14个典型预设属性参数化验证 --- tests/unit/test_transition_presets.py | 484 ++++++++++++++++++-------- 1 file changed, 335 insertions(+), 149 deletions(-) diff --git a/tests/unit/test_transition_presets.py b/tests/unit/test_transition_presets.py index ce2db1f0a..c16c08fd7 100755 --- a/tests/unit/test_transition_presets.py +++ b/tests/unit/test_transition_presets.py @@ -1,6 +1,6 @@ -from dataclasses import FrozenInstanceError +"""转场预设库单元测试.""" -"""transition_presets 领域层单元测试 - 转场预设库""" +from __future__ import annotations import pytest @@ -12,198 +12,384 @@ from packages.domain.transition_presets import ( list_transition_presets, ) +# ── 数据类测试 ──────────────────────────────────────────────────────────────── + class TestTransitionPreset: - """TransitionPreset 数据类测试""" + """TransitionPreset 数据类测试.""" - def test_create_minimal(self): - preset = TransitionPreset(id="test", name="测试", category="basic", transition="fade") - assert preset.id == "test" - assert preset.name == "测试" - assert preset.category == "basic" - assert preset.transition == "fade" - assert preset.description == "" - assert preset.tags == [] - assert preset.default_duration == 0.5 - assert preset.min_duration == 0.1 - assert preset.max_duration == 3.0 - assert preset.has_custom_params is False - - def test_create_with_all_params(self): - preset = TransitionPreset( - id="custom", - name="自定义转场", - category="special", - description="炫酷特效", - tags=["炫酷", "特效"], - transition="custom", - default_duration=1.0, - min_duration=0.5, - max_duration=5.0, - has_custom_params=True, + def test_basic_attributes(self): + """基础属性可访问.""" + p = TransitionPreset( + id="test_id", + name="测试转场", + category="fade", + description="测试描述", + tags=["tag1", "tag2"], + transition="fade", + default_duration=0.5, + min_duration=0.1, + max_duration=3.0, + has_custom_params=False, ) - assert preset.description == "炫酷特效" - assert preset.tags == ["炫酷", "特效"] - assert preset.default_duration == 1.0 - assert preset.min_duration == 0.5 - assert preset.max_duration == 5.0 - assert preset.has_custom_params is True + assert p.id == "test_id" + assert p.name == "测试转场" + assert p.category == "fade" + assert p.description == "测试描述" + assert p.tags == ["tag1", "tag2"] + assert p.transition == "fade" + assert p.default_duration == 0.5 + assert p.min_duration == 0.1 + assert p.max_duration == 3.0 + assert p.has_custom_params is False + + def test_default_values(self): + """默认值正确.""" + p = TransitionPreset(id="t", name="T", category="basic") + assert p.description == "" + assert p.tags == [] + assert p.transition == "fade" + assert p.default_duration == 0.5 + assert p.min_duration == 0.1 + assert p.max_duration == 3.0 + assert p.has_custom_params is False def test_frozen_immutable(self): - """frozen dataclass 不可修改""" - preset = TransitionPreset(id="test", name="测试", category="basic") - with pytest.raises(FrozenInstanceError): - preset.name = "改名" + """frozen dataclass 不可修改.""" + p = TransitionPreset(id="t", name="T", category="basic") + with pytest.raises(Exception): # FrozenInstanceError + p.name = "新名字" - def test_tags_default_empty_list(self): - preset = TransitionPreset(id="t1", name="t1", category="basic") - preset2 = TransitionPreset(id="t2", name="t2", category="basic") - assert preset.tags == [] - assert preset.tags is not preset2.tags + def test_not_hashable_due_to_list(self): + """含list字段(tags)的frozen dataclass不可哈希(list可变).""" + p = TransitionPreset(id="t", name="T", category="basic") + with pytest.raises(TypeError): + hash(p) - def test_default_transition_is_fade(self): - preset = TransitionPreset(id="test", name="测试", category="basic") - assert preset.transition == "fade" + def test_equality(self): + """相同属性的实例相等.""" + p1 = TransitionPreset(id="t", name="T", category="basic") + p2 = TransitionPreset(id="t", name="T", category="basic") + assert p1 == p2 + + def test_inequality(self): + """不同属性的实例不等.""" + p1 = TransitionPreset(id="t1", name="T", category="basic") + p2 = TransitionPreset(id="t2", name="T", category="basic") + assert p1 != p2 + + +# ── 预设库完整性测试 ────────────────────────────────────────────────────────── class TestTransitionPresetLibrary: - """TRANSITION_PRESET_LIBRARY 预设库测试""" + """TRANSITION_PRESET_LIBRARY 预设库完整性测试.""" def test_library_not_empty(self): + """预设库不为空.""" assert len(TRANSITION_PRESET_LIBRARY) > 0 - def test_all_presets_have_unique_ids(self): - """所有预设 ID 唯一""" + def test_preset_count(self): + """预设数量应大于20.""" + assert len(TRANSITION_PRESET_LIBRARY) >= 20 + + def test_all_ids_unique(self): + """所有预设ID唯一.""" ids = [p.id for p in TRANSITION_PRESET_LIBRARY] - assert len(ids) == len(set(ids)) + assert len(ids) == len(set(ids)), f"存在重复ID: {[i for i in ids if ids.count(i) > 1]}" - def test_all_presets_have_required_fields(self): - """所有预设都有必填字段""" - for preset in TRANSITION_PRESET_LIBRARY: - assert preset.id, "missing id" - assert preset.name, f"{preset.id} missing name" - assert preset.category, f"{preset.id} missing category" - assert preset.transition, f"{preset.id} missing transition" + def test_all_have_required_fields(self): + """所有预设都有必填字段.""" + for p in TRANSITION_PRESET_LIBRARY: + assert p.id, f"预设缺少id: {p}" + assert p.name, f"预设 {p.id} 缺少name" + assert p.category, f"预设 {p.id} 缺少category" + assert p.transition, f"预设 {p.id} 缺少transition" - def test_transition_none_exists(self): - """无转场预设存在""" - none_preset = next((p for p in TRANSITION_PRESET_LIBRARY if p.id == "transition_none"), None) + def test_duration_range_valid(self): + """每个预设的时长范围合理: min <= default <= max.""" + for p in TRANSITION_PRESET_LIBRARY: + assert ( + p.min_duration <= p.default_duration + ), f"{p.id}: min({p.min_duration}) > default({p.default_duration})" + assert ( + p.default_duration <= p.max_duration + ), f"{p.id}: default({p.default_duration}) > max({p.max_duration})" + + def test_min_duration_non_negative(self): + """最小时长不能为负.""" + for p in TRANSITION_PRESET_LIBRARY: + assert p.min_duration >= 0, f"{p.id}: min_duration为负" + + def test_categories_are_valid(self): + """分类都在预期集合内.""" + valid_categories = {"basic", "fade", "slide", "zoom", "warp", "special"} + for p in TRANSITION_PRESET_LIBRARY: + assert p.category in valid_categories, f"{p.id}: 未知分类 {p.category}" + + @pytest.mark.parametrize( + "category,expected_min", + [ + ("basic", 2), + ("fade", 4), + ("slide", 4), + ("zoom", 2), + ("warp", 6), + ("special", 2), + ], + ) + def test_category_min_count(self, category: str, expected_min: int): + """每个分类至少有预期数量的预设.""" + count = sum(1 for p in TRANSITION_PRESET_LIBRARY if p.category == category) + assert count >= expected_min, f"分类 {category} 只有 {count} 个,预期至少 {expected_min}" + + def test_tags_is_list(self): + """tags字段是列表.""" + for p in TRANSITION_PRESET_LIBRARY: + assert isinstance(p.tags, list), f"{p.id}: tags不是列表" + + def test_none_transition_has_zero_duration(self): + """无转场预设时长为0.""" + none_preset = get_transition_preset("transition_none") assert none_preset is not None - assert none_preset.name == "无转场" + assert none_preset.min_duration == 0.0 + assert none_preset.max_duration == 0.0 + assert none_preset.default_duration == 0.0 assert none_preset.transition == "none" - def test_transition_random_exists(self): - """随机转场预设存在""" - random_preset = next((p for p in TRANSITION_PRESET_LIBRARY if p.id == "transition_random"), None) - assert random_preset is not None - assert random_preset.name == "随机" - def test_fade_category_exists(self): - """淡入淡出分类有预设""" - fade_presets = [p for p in TRANSITION_PRESET_LIBRARY if p.category == "fade"] - assert len(fade_presets) >= 2 - - def test_duration_constraints_valid(self): - """时长约束:min <= default <= max""" - for preset in TRANSITION_PRESET_LIBRARY: - assert preset.min_duration <= preset.default_duration, f"{preset.id}: min > default" - assert preset.default_duration <= preset.max_duration, f"{preset.id}: default > max" - assert preset.min_duration >= 0, f"{preset.id}: min < 0" - - def test_known_categories_exist(self): - """已知分类都有预设""" - categories = {p.category for p in TRANSITION_PRESET_LIBRARY} - assert "basic" in categories - assert "fade" in categories +# ── get_transition_preset 测试 ──────────────────────────────────────────────── class TestGetTransitionPreset: - """get_transition_preset 函数测试""" + """get_transition_preset 函数测试.""" def test_get_existing_preset(self): - preset = get_transition_preset("transition_none") - assert preset is not None - assert preset.id == "transition_none" - - def test_get_fade_preset(self): - preset = get_transition_preset("transition_fade") - assert preset is not None - assert preset.transition == "fade" + """获取存在的预设.""" + p = get_transition_preset("transition_fade") + assert p is not None + assert p.id == "transition_fade" + assert p.name == "淡入淡出" + assert p.category == "fade" def test_get_nonexistent_preset(self): - assert get_transition_preset("nonexistent_transition") is None + """获取不存在的预设返回None.""" + p = get_transition_preset("nonexistent_id") + assert p is None - def test_returns_transitionpreset_type(self): - preset = get_transition_preset("transition_fade") - assert isinstance(preset, TransitionPreset) + def test_get_none_preset(self): + """获取无转场预设.""" + p = get_transition_preset("transition_none") + assert p is not None + assert p.transition == "none" + + def test_get_random_preset(self): + """获取随机预设.""" + p = get_transition_preset("transition_random") + assert p is not None + assert p.transition == "random" + + def test_case_sensitive(self): + """ID区分大小写.""" + p = get_transition_preset("TRANSITION_FADE") + assert p is None + + def test_empty_string(self): + """空字符串返回None.""" + p = get_transition_preset("") + assert p is None + + def test_returns_same_instance(self): + """多次调用返回同一个对象(库引用).""" + p1 = get_transition_preset("transition_fade") + p2 = get_transition_preset("transition_fade") + assert p1 is p2 + + def test_all_presets_accessible_by_id(self): + """所有预设都可通过ID获取.""" + for p in TRANSITION_PRESET_LIBRARY: + fetched = get_transition_preset(p.id) + assert fetched is not None, f"无法通过ID获取: {p.id}" + assert fetched.id == p.id + + +# ── list_transition_presets 测试 ────────────────────────────────────────────── class TestListTransitionPresets: - """list_transition_presets 函数测试""" + """list_transition_presets 函数测试.""" - def test_list_all(self): - """不带参数返回所有预设""" - all_presets = list_transition_presets() - assert len(all_presets) == len(TRANSITION_PRESET_LIBRARY) + def test_no_filter_returns_all(self): + """无筛选参数返回全部预设.""" + results = list_transition_presets() + assert len(results) == len(TRANSITION_PRESET_LIBRARY) - def test_filter_by_category(self): - """按分类筛选""" - fade_presets = list_transition_presets(category="fade") - assert len(fade_presets) > 0 - assert all(p.category == "fade" for p in fade_presets) + def test_filter_by_category_fade(self): + """按fade分类筛选.""" + results = list_transition_presets(category="fade") + assert len(results) > 0 + assert all(p.category == "fade" for p in results) - def test_filter_by_basic_category(self): - basic_presets = list_transition_presets(category="basic") - assert len(basic_presets) >= 2 + def test_filter_by_category_slide(self): + """按slide分类筛选.""" + results = list_transition_presets(category="slide") + assert len(results) == 4 + assert all(p.category == "slide" for p in results) - def test_filter_by_nonexistent_category(self): - result = list_transition_presets(category="nonexistent") - assert result == [] + def test_filter_by_category_basic(self): + """按basic分类筛选.""" + results = list_transition_presets(category="basic") + assert len(results) == 2 # none + random - def test_search_by_name(self): - """按名称搜索""" - result = list_transition_presets(keyword="淡入") - assert len(result) >= 1 - assert any("淡入" in p.name for p in result) + def test_filter_by_invalid_category(self): + """无效分类返回空列表.""" + results = list_transition_presets(category="nonexistent") + assert results == [] - def test_search_by_tag(self): - """按标签搜索""" - tagged = [p for p in TRANSITION_PRESET_LIBRARY if p.tags] - if tagged: - tag = tagged[0].tags[0] - result = list_transition_presets(keyword=tag) - assert len(result) >= 1 + def test_keyword_search_in_name(self): + """关键词搜索name字段.""" + results = list_transition_presets(keyword="淡入淡出") + assert len(results) >= 1 + assert any(p.id == "transition_fade" for p in results) - def test_search_empty_returns_all(self): - result = list_transition_presets(keyword="") - assert len(result) == len(TRANSITION_PRESET_LIBRARY) + def test_keyword_search_in_description(self): + """关键词搜索description字段.""" + results = list_transition_presets(keyword="硬切") + assert len(results) >= 1 + assert any(p.id == "transition_none" for p in results) - def test_combined_category_and_search(self): - result = list_transition_presets(category="fade", keyword="淡入") - assert all(p.category == "fade" for p in result) + def test_keyword_search_in_tags(self): + """关键词搜索tags字段.""" + results = list_transition_presets(keyword="模糊") + assert len(results) >= 2 # hblur + wipeblur + ids = [p.id for p in results] + assert "transition_hblur" in ids + assert "transition_wipeblur" in ids - def test_returns_list_of_transitionpreset(self): - result = list_transition_presets() - assert all(isinstance(p, TransitionPreset) for p in result) + def test_keyword_case_insensitive(self): + """关键词搜索不区分大小写(英文).""" + results1 = list_transition_presets(keyword="fade") + results2 = list_transition_presets(keyword="FADE") + assert len(results1) == len(results2) + + def test_keyword_chinese(self): + """中文关键词搜索.""" + results = list_transition_presets(keyword="滑") + assert len(results) >= 4 # 4个slide + assert all("滑" in p.name for p in results) + + def test_keyword_no_match(self): + """无匹配关键词返回空.""" + results = list_transition_presets(keyword="完全不存在的关键词xyz") + assert results == [] + + def test_keyword_empty_string(self): + """空关键词返回全部.""" + results = list_transition_presets(keyword="") + assert len(results) == len(TRANSITION_PRESET_LIBRARY) + + def test_category_and_keyword_combined(self): + """分类+关键词组合筛选.""" + results = list_transition_presets(category="warp", keyword="擦除") + assert len(results) >= 4 # 4个wipe + assert all(p.category == "warp" for p in results) + assert all("擦除" in p.name for p in results) + + def test_category_and_keyword_no_match(self): + """分类+关键词不匹配返回空.""" + results = list_transition_presets(category="fade", keyword="滑动") + assert results == [] + + def test_preserve_order(self): + """保持预设库的顺序.""" + results = list_transition_presets() + for i, p in enumerate(TRANSITION_PRESET_LIBRARY): + assert results[i].id == p.id + + def test_filtered_results_are_all_valid(self): + """筛选结果的每个预设都有完整属性.""" + results = list_transition_presets(category="slide") + for p in results: + assert p.id + assert p.name + assert p.category == "slide" + assert isinstance(p.tags, list) + + +# ── get_default_transition 测试 ─────────────────────────────────────────────── class TestGetDefaultTransition: - """get_default_transition 函数测试""" - - def test_returns_preset(self): - preset = get_default_transition() - assert preset is not None - assert isinstance(preset, TransitionPreset) + """get_default_transition 函数测试.""" def test_default_is_none(self): - """默认转场是无转场(硬切)""" - preset = get_default_transition() - assert preset.id == "transition_none" - assert preset.transition == "none" + """默认转场是无转场.""" + p = get_default_transition() + assert p.id == "transition_none" + assert p.transition == "none" - def test_default_has_zero_duration(self): - """无转场默认时长为 0""" - preset = get_default_transition() - assert preset.default_duration == 0.0 - assert preset.min_duration == 0.0 - assert preset.max_duration == 0.0 + def test_default_zero_duration(self): + """默认转场时长为0.""" + p = get_default_transition() + assert p.default_duration == 0.0 + assert p.min_duration == 0.0 + assert p.max_duration == 0.0 + + def test_default_category_basic(self): + """默认转场属于basic分类.""" + p = get_default_transition() + assert p.category == "basic" + + def test_default_same_instance(self): + """多次调用返回同一实例.""" + p1 = get_default_transition() + p2 = get_default_transition() + assert p1 is p2 + + def test_default_matches_get_preset(self): + """默认转场与通过ID获取的一致.""" + default = get_default_transition() + by_id = get_transition_preset("transition_none") + assert default is by_id + + +# ── 预设个体属性抽样测试 ────────────────────────────────────────────────────── + + +class TestPresetSamples: + """典型预设的属性验证.""" + + @pytest.mark.parametrize( + "preset_id,expected_name,expected_category,expected_transition", + [ + ("transition_none", "无转场", "basic", "none"), + ("transition_random", "随机", "basic", "random"), + ("transition_fade", "淡入淡出", "fade", "fade"), + ("transition_fadeblack", "黑场过渡", "fade", "fadeblack"), + ("transition_fadewhite", "白场过渡", "fade", "fadewhite"), + ("transition_slideleft", "左滑", "slide", "slideleft"), + ("transition_slideright", "右滑", "slide", "slideright"), + ("transition_slideup", "上滑", "slide", "slideup"), + ("transition_slidedown", "下滑", "slide", "slidedown"), + ("transition_zoomin", "放大进入", "zoom", "zoomin"), + ("transition_zoomout", "缩小退出", "zoom", "zoomout"), + ("transition_dissolve", "溶解", "warp", "dissolve"), + ("transition_circlecrop", "圆形展开", "warp", "circlecrop"), + ("transition_hblur", "水平模糊", "special", "hblur"), + ], + ) + def test_preset_attributes( + self, preset_id: str, expected_name: str, expected_category: str, expected_transition: str + ): + """典型预设属性验证.""" + p = get_transition_preset(preset_id) + assert p is not None + assert p.name == expected_name + assert p.category == expected_category + assert p.transition == expected_transition + + def test_dissolve_longer_default(self): + """溶解效果默认时长较长(0.8s).""" + p = get_transition_preset("transition_dissolve") + assert p is not None + assert p.default_duration == 0.8 -- 2.54.0