diff --git a/tests/unit/test_voice_presets.py b/tests/unit/test_voice_presets.py index 2d2623ef1..a307ecbac 100755 --- a/tests/unit/test_voice_presets.py +++ b/tests/unit/test_voice_presets.py @@ -1,4 +1,6 @@ -"""voice_presets 音色预设单测.""" +"""voice_presets 配音音色预设单元测试.""" + +from __future__ import annotations import pytest @@ -12,126 +14,389 @@ from packages.domain.voice_presets import ( list_voices, ) +# ── VoiceGender 枚举测试 ───────────────────────────────────────────────────── + class TestVoiceGender: - def test_values(self): + """VoiceGender 枚举测试.""" + + def test_male(self): + """男声.""" assert VoiceGender.MALE.value == "male" + + def test_female(self): + """女声.""" assert VoiceGender.FEMALE.value == "female" + + def test_child(self): + """童声.""" assert VoiceGender.CHILD.value == "child" - def test_is_str(self): - assert isinstance(VoiceGender.FEMALE, str) + def test_count(self): + """共3种性别.""" + assert len(VoiceGender) == 3 + + def test_is_str_enum(self): + """可与字符串比较.""" + assert VoiceGender.FEMALE == "female" + + +# ── VoiceStyle 枚举测试 ────────────────────────────────────────────────────── class TestVoiceStyle: - def test_values(self): + """VoiceStyle 枚举测试.""" + + def test_stable(self): + """沉稳.""" assert VoiceStyle.STABLE.value == "stable" + + def test_lively(self): + """活泼.""" assert VoiceStyle.LIVELY.value == "lively" + + def test_customer_service(self): + """客服.""" + assert VoiceStyle.CUSTOMER_SERVICE.value == "customer_service" + + def test_narration(self): + """旁白.""" assert VoiceStyle.NARRATION.value == "narration" + + def test_news(self): + """新闻.""" assert VoiceStyle.NEWS.value == "news" + + def test_story(self): + """故事.""" assert VoiceStyle.STORY.value == "story" + def test_count(self): + """共6种风格.""" + assert len(VoiceStyle) == 6 + + +# ── VoicePreset 数据类测试 ─────────────────────────────────────────────────── + class TestVoicePreset: + """VoicePreset 数据类测试.""" + + def test_minimal_creation(self): + """最小化创建(必填字段).""" + v = VoicePreset(voice_id="v1", name="测试音色") + assert v.voice_id == "v1" + assert v.name == "测试音色" + def test_default_values(self): - v = VoicePreset(voice_id="test", name="测试音色") + """默认值正确.""" + v = VoicePreset(voice_id="v1", name="T") assert v.gender == VoiceGender.FEMALE assert v.style == VoiceStyle.NARRATION + assert v.description == "" assert v.provider == "mock" + assert v.provider_voice_id == "" assert v.default_speed == 1.0 assert v.default_pitch == 0.0 assert v.sample_rate == 22050 assert v.language == "zh-CN" - def test_custom_values(self): + def test_full_creation(self): + """完整字段创建.""" v = VoicePreset( - voice_id="male1", - name="男声", + voice_id="voice_full", + name="完整音色", gender=VoiceGender.MALE, - style=VoiceStyle.STABLE, + style=VoiceStyle.NEWS, + description="测试描述", provider="aliyun", + provider_voice_id="ali_001", default_speed=0.9, + default_pitch=1.5, + sample_rate=44100, + language="en-US", ) + assert v.voice_id == "voice_full" + assert v.name == "完整音色" assert v.gender == VoiceGender.MALE - assert v.style == VoiceStyle.STABLE + assert v.style == VoiceStyle.NEWS + assert v.description == "测试描述" assert v.provider == "aliyun" + assert v.provider_voice_id == "ali_001" assert v.default_speed == 0.9 + assert v.default_pitch == 1.5 + assert v.sample_rate == 44100 + assert v.language == "en-US" + + +# ── MOCK_VOICES 列表测试 ───────────────────────────────────────────────────── class TestMockVoices: - def test_mock_voices_not_empty(self): + """MOCK_VOICES 预设列表测试.""" + + def test_not_empty(self): + """列表不为空.""" assert len(MOCK_VOICES) > 0 - def test_all_mock_voices_have_ids(self): - for v in MOCK_VOICES: - assert v.voice_id - assert v.name - assert v.provider == "mock" + def test_count(self): + """共8个Mock音色.""" + assert len(MOCK_VOICES) == 8 - def test_unique_voice_ids(self): + def test_all_have_voice_id(self): + """每个音色都有voice_id.""" + for v in MOCK_VOICES: + assert v.voice_id, f"音色缺少voice_id: {v}" + + def test_all_have_name(self): + """每个音色都有name.""" + for v in MOCK_VOICES: + assert v.name, f"{v.voice_id} 缺少name" + + def test_voice_ids_unique(self): + """voice_id唯一.""" ids = [v.voice_id for v in MOCK_VOICES] - 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_mock_provider(self): + """都是mock供应商.""" + for v in MOCK_VOICES: + assert v.provider == "mock", f"{v.voice_id} provider不是mock" + + def test_all_chinese(self): + """都是中文语言.""" + for v in MOCK_VOICES: + assert v.language == "zh-CN" + + def test_sample_rate_consistent(self): + """采样率一致为22050.""" + for v in MOCK_VOICES: + assert v.sample_rate == 22050 + + def test_gender_distribution(self): + """性别分布:至少有男/女/童声.""" + genders = {v.gender for v in MOCK_VOICES} + assert VoiceGender.MALE in genders + assert VoiceGender.FEMALE in genders + assert VoiceGender.CHILD in genders + + def test_style_coverage(self): + """覆盖多种风格.""" + styles = {v.style for v in MOCK_VOICES} + assert len(styles) >= 4 + + def test_specific_voices_exist(self): + """特定音色存在.""" + ids = {v.voice_id for v in MOCK_VOICES} + assert "female_warm" in ids + assert "male_stable" in ids + assert "female_lively" in ids + assert "child_cute" in ids + + def test_default_speed_positive(self): + """语速都大于0.""" + for v in MOCK_VOICES: + assert v.default_speed > 0, f"{v.voice_id}语速<=0" + + def test_descriptions_not_empty(self): + """都有描述.""" + for v in MOCK_VOICES: + assert v.description, f"{v.voice_id}缺少描述" + assert len(v.description) > 5 + + +# ── get_voice 测试 ─────────────────────────────────────────────────────────── class TestGetVoice: + """get_voice 函数测试.""" + def test_get_existing_voice(self): + """获取存在的音色.""" v = get_voice("female_warm") assert v is not None assert v.voice_id == "female_warm" assert v.name == "温暖女声" - def test_get_nonexistent_voice(self): - v = get_voice("nonexistent") - assert v is None + def test_get_male_stable(self): + """获取沉稳男声.""" + v = get_voice("male_stable") + assert v is not None + assert v.gender == VoiceGender.MALE + assert v.style == VoiceStyle.STABLE - def test_non_mock_provider_returns_none(self): - v = get_voice("female_warm", provider="aliyun") - assert v is None + def test_get_nonexistent_voice(self): + """获取不存在的音色返回None.""" + assert get_voice("nonexistent") is None + + def test_get_empty_string(self): + """空字符串返回None.""" + assert get_voice("") is None + + def test_mock_provider(self): + """指定mock provider.""" + v = get_voice("female_warm", provider="mock") + assert v is not None + assert v.provider == "mock" + + def test_other_provider_returns_none(self): + """其他provider返回None.""" + assert get_voice("female_warm", provider="aliyun") is None + assert get_voice("female_warm", provider="xunfei") is None + + def test_returns_same_instance(self): + """返回同一个对象引用.""" + v1 = get_voice("female_warm") + v2 = get_voice("female_warm") + assert v1 is v2 + + +# ── list_voices 测试 ───────────────────────────────────────────────────────── class TestListVoices: - def test_list_all(self): - voices = list_voices() - assert len(voices) == len(MOCK_VOICES) + """list_voices 筛选函数测试.""" - def test_filter_by_gender(self): - female_voices = list_voices(gender="female") - assert len(female_voices) > 0 - assert all(v.gender == VoiceGender.FEMALE for v in female_voices) + def test_no_filter_returns_all(self): + """无筛选返回全部.""" + result = list_voices() + assert len(result) == len(MOCK_VOICES) - def test_filter_by_style(self): - story_voices = list_voices(style="story") - assert len(story_voices) > 0 - assert all(v.style == VoiceStyle.STORY for v in story_voices) + def test_filter_by_gender_female(self): + """按女性筛选.""" + result = list_voices(gender="female") + assert len(result) > 0 + assert all(v.gender == VoiceGender.FEMALE for v in result) - def test_filter_by_keyword_name(self): - voices = list_voices(keyword="女声") - assert len(voices) > 0 - assert all("女声" in v.name for v in voices) + def test_filter_by_gender_male(self): + """按男性筛选.""" + result = list_voices(gender="male") + assert len(result) > 0 + assert all(v.gender == VoiceGender.MALE for v in result) - def test_filter_by_keyword_description(self): - voices = list_voices(keyword="商务") - assert len(voices) > 0 - assert any("商务" in v.description for v in voices) + def test_filter_by_gender_child(self): + """按童声筛选.""" + result = list_voices(gender="child") + assert len(result) >= 1 + assert all(v.gender == VoiceGender.CHILD for v in result) - def test_filter_by_provider_non_mock(self): - voices = list_voices(provider="aliyun") - assert len(voices) == 0 + def test_filter_by_invalid_gender(self): + """无效性别返回空.""" + result = list_voices(gender="alien") + assert result == [] - def test_filter_multiple_conditions(self): - voices = list_voices(gender="female", style="narration") - assert len(voices) > 0 - assert all(v.gender == VoiceGender.FEMALE for v in voices) - assert all(v.style == VoiceStyle.NARRATION for v in voices) + def test_filter_by_style_narration(self): + """按旁白风格筛选.""" + result = list_voices(style="narration") + assert len(result) > 0 + assert all(v.style == VoiceStyle.NARRATION for v in result) + + def test_filter_by_style_news(self): + """按新闻风格筛选.""" + result = list_voices(style="news") + assert len(result) >= 1 + assert all(v.style == VoiceStyle.NEWS for v in result) + + def test_filter_by_style_story(self): + """按故事风格筛选.""" + result = list_voices(style="story") + assert len(result) >= 1 + assert all(v.style == VoiceStyle.STORY for v in result) + + def test_filter_by_invalid_style(self): + """无效风格返回空.""" + result = list_voices(style="unknown") + assert result == [] + + def test_filter_by_provider_mock(self): + """mock provider返回全部.""" + result = list_voices(provider="mock") + assert len(result) == len(MOCK_VOICES) + + def test_filter_by_provider_other(self): + """其他provider返回空.""" + result = list_voices(provider="aliyun") + assert result == [] + + def test_keyword_search_name(self): + """关键词搜索name.""" + result = list_voices(keyword="温暖") + assert len(result) >= 1 + assert any(v.voice_id == "female_warm" for v in result) + + def test_keyword_search_description(self): + """关键词搜索description.""" + result = list_voices(keyword="商务") + assert len(result) >= 1 + assert any("商务" in v.description for v in result) + + def test_keyword_search_voice_id(self): + """关键词搜索voice_id.""" + result = list_voices(keyword="male_stable") + assert len(result) >= 1 + assert result[0].voice_id == "male_stable" def test_keyword_case_insensitive(self): - voices1 = list_voices(keyword="FEMALE") - voices2 = list_voices(keyword="female") - assert len(voices1) == len(voices2) + """关键词不区分大小写(英文).""" + r1 = list_voices(keyword="Female") + r2 = list_voices(keyword="female") + assert len(r1) == len(r2) + + def test_keyword_no_match(self): + """无匹配关键词返回空.""" + result = list_voices(keyword="完全不存在的音色xyz") + assert result == [] + + def test_gender_and_style_combined(self): + """性别+风格组合筛选.""" + result = list_voices(gender="female", style="story") + assert len(result) >= 1 + assert all(v.gender == VoiceGender.FEMALE for v in result) + assert all(v.style == VoiceStyle.STORY for v in result) + + def test_returns_new_list(self): + """返回新列表,修改不影响原数据.""" + result = list_voices() + result.clear() + assert len(MOCK_VOICES) == 8 + + def test_empty_keyword_returns_all(self): + """空关键词返回全部.""" + result = list_voices(keyword="") + assert len(result) == len(MOCK_VOICES) + + def test_none_keyword_returns_all(self): + """None关键词返回全部.""" + result = list_voices(keyword=None) + assert len(result) == len(MOCK_VOICES) + + +# ── get_default_voice 测试 ─────────────────────────────────────────────────── class TestGetDefaultVoice: + """get_default_voice 测试.""" + def test_default_voice_exists(self): + """默认音色存在.""" v = get_default_voice() assert v is not None - assert v == MOCK_VOICES[0] + assert isinstance(v, VoicePreset) + + def test_default_is_first_mock(self): + """默认音色是列表第一个.""" + v = get_default_voice() + assert v is MOCK_VOICES[0] + + def test_default_is_female_warm(self): + """默认是温暖女声.""" + v = get_default_voice() + assert v.voice_id == "female_warm" + assert v.name == "温暖女声" + assert v.gender == VoiceGender.FEMALE + + def test_default_same_instance(self): + """多次调用返回同一实例.""" + v1 = get_default_voice() + v2 = get_default_voice() + assert v1 is v2