Files
xiaoxia-saas/tests/unit/test_tts_engine_pure.py
T
CI Bot 548fc9beb5
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 59s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m1s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m11s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 29s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 45s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 55s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m17s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 52s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 26s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m35s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 5m31s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m0s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m8s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 9m1s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
test(unit): 第70波 - 去重算法 + 字幕渲染 + TTS引擎纯逻辑
- dedup: hamming_distance汉明距离(8种场景) + VideoFingerprint数据结构 + _average_histogram_similarity直方图相似度(10种边界)
- render_subtitles: 颜色转换/位置映射/样式构建/文本转义/时间格式化 + generate_ass_subtitles文件生成(12种场景)
- tts_engine: VoiceoverSegment/VoiceoverResult数据结构 + generate_full_voiceover边界(7种) + generate_subtitle_voiceover边界(3种)
+73 tests 全绿
2026-07-25 10:04:32 +08:00

185 lines
6.6 KiB
Python
Executable File
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.
"""TTS配音引擎纯逻辑测试 — 数据结构 + 边界情况(mock TTS服务)."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from packages.domain.tts_config import TtsConfig
from packages.ports.tts_service import TtsError
from video_processing.tts_engine import (
TtsEngine,
VoiceoverResult,
VoiceoverSegment,
)
class TestVoiceoverSegment:
"""VoiceoverSegment 数据结构测试."""
def test_default_values(self):
"""默认值正确."""
seg = VoiceoverSegment(text="你好")
assert seg.text == "你好"
assert seg.start_time == 0.0
assert seg.end_time == 0.0
assert seg.audio_path is None
assert seg.duration == 0.0
def test_full_values(self):
"""完整字段."""
seg = VoiceoverSegment(
text="测试",
start_time=1.5,
end_time=3.5,
audio_path=Path("/tmp/test.wav"),
duration=2.0,
)
assert seg.text == "测试"
assert seg.start_time == 1.5
assert seg.end_time == 3.5
assert seg.audio_path == Path("/tmp/test.wav")
assert seg.duration == 2.0
class TestVoiceoverResult:
"""VoiceoverResult 数据结构测试."""
def test_failure_default(self):
"""失败结果默认值."""
result = VoiceoverResult(success=False)
assert result.success is False
assert result.segments == []
assert result.total_duration == 0.0
assert result.error_message == ""
def test_success_with_segments(self):
"""成功结果带片段."""
seg = VoiceoverSegment(text="hi", duration=5.0)
result = VoiceoverResult(
success=True,
segments=[seg],
total_duration=5.0,
)
assert result.success is True
assert len(result.segments) == 1
assert result.total_duration == 5.0
def test_failure_with_message(self):
"""失败带错误信息."""
result = VoiceoverResult(success=False, error_message="TTS出错")
assert result.success is False
assert result.error_message == "TTS出错"
class TestTtsEngineFullVoiceover:
"""TtsEngine.generate_full_voiceover 整段配音测试(mock TTS."""
def _make_engine(self, tmp_path: Path, tts: MagicMock | None = None) -> TtsEngine:
"""创建测试用TtsEngine."""
if tts is None:
tts = MagicMock()
tts.synthesize.return_value = str(tmp_path / "out.wav")
return TtsEngine(tts_service=tts, work_dir=tmp_path)
def test_disabled_returns_failure(self, tmp_path):
"""配音未启用→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=False, text="测试")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "未启用" in result.error_message
assert len(result.segments) == 0
def test_empty_text_returns_failure(self, tmp_path):
"""文本为空→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=True, text=" ")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "文本为空" in result.error_message
def test_success_creates_segment(self, tmp_path):
"""成功合成返回正确结构."""
mock_tts = MagicMock()
output_file = tmp_path / "voiceover_full.wav"
output_file.write_bytes(b"fake audio")
mock_tts.synthesize.return_value = str(output_file)
engine = self._make_engine(tmp_path, mock_tts)
config = TtsConfig(enabled=True, text="测试文本", voice_id="female_warm", speed=1.0)
result = engine.generate_full_voiceover(config)
assert result.success is True
assert len(result.segments) == 1
assert result.segments[0].text == "测试文本"
assert result.segments[0].start_time == 0.0
assert result.total_duration > 0
mock_tts.synthesize.assert_called_once()
def test_tts_error_returns_failure_gracefully(self, tmp_path):
"""TTS抛错→优雅降级返回失败."""
mock_tts = MagicMock()
mock_tts.synthesize.side_effect = TtsError("合成失败")
engine = self._make_engine(tmp_path, mock_tts)
config = TtsConfig(enabled=True, text="测试")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "合成失败" in result.error_message
def test_generic_exception_returns_failure(self, tmp_path):
"""其他异常也降级返回失败."""
mock_tts = MagicMock()
mock_tts.synthesize.side_effect = RuntimeError("未知错误")
engine = self._make_engine(tmp_path, mock_tts)
config = TtsConfig(enabled=True, text="测试")
result = engine.generate_full_voiceover(config)
assert result.success is False
assert "未知错误" in result.error_message
def test_work_dir_created(self, tmp_path):
"""工作目录自动创建."""
new_dir = tmp_path / "nested" / "tts"
mock_tts = MagicMock()
TtsEngine(tts_service=mock_tts, work_dir=new_dir)
assert new_dir.exists()
class TestTtsEngineSubtitleVoiceover:
"""TtsEngine.generate_subtitle_voiceover 字幕配音测试(mock TTS."""
def _make_engine(self, tmp_path: Path, tts: MagicMock | None = None) -> TtsEngine:
if tts is None:
tts = MagicMock()
return TtsEngine(tts_service=tts, work_dir=tmp_path)
def test_disabled_returns_failure(self, tmp_path):
"""配音未启用→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=False, text="")
result = engine.generate_subtitle_voiceover(config, [{"text": "hi", "start_time": 0, "end_time": 1}])
assert result.success is False
assert "未启用" in result.error_message
def test_empty_subtitles_returns_failure(self, tmp_path):
"""字幕列表为空→失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=True, text="")
result = engine.generate_subtitle_voiceover(config, [])
assert result.success is False
assert "字幕为空" in result.error_message
def test_none_subtitles_returns_failure(self, tmp_path):
"""None字幕也失败."""
engine = self._make_engine(tmp_path)
config = TtsConfig(enabled=True, text="")
result = engine.generate_subtitle_voiceover(config, None) # type: ignore
assert result.success is False