- Refactor codebase into src/ (preprocessing, translation, assembly) - Add pipeline/ scripts for individual stages - Externalize configuration to config/config.yaml - Fix Cover Image preservation - Update documentation and manuals
188 lines
6.4 KiB
Python
188 lines
6.4 KiB
Python
"""
|
|
翻译器测试
|
|
"""
|
|
|
|
import pytest
|
|
import asyncio
|
|
from unittest.mock import Mock, AsyncMock, patch
|
|
|
|
from src.translator import EPUBTranslator
|
|
from src.llm_client import OpenRouterClient
|
|
|
|
|
|
class TestEPUBTranslator:
|
|
"""EPUB 翻译器测试类"""
|
|
|
|
@pytest.fixture
|
|
def mock_config(self):
|
|
"""模拟配置"""
|
|
return {
|
|
'openrouter': {
|
|
'api_key': 'test_key',
|
|
'base_url': 'https://openrouter.ai/api/v1',
|
|
'models': {
|
|
'test': 'google/gemini-2.0-flash-exp',
|
|
'production': 'google/gemini-exp-1206'
|
|
},
|
|
'rate_limits': {
|
|
'requests_per_minute': 60,
|
|
'concurrent_requests': 5
|
|
}
|
|
},
|
|
'translation': {
|
|
'chunk_size': 3,
|
|
'max_context_length': 8000,
|
|
'sample_ratio': 0.1,
|
|
'target_language': 'zh-CN',
|
|
'temperature': 0.3,
|
|
'max_tokens': 4000
|
|
},
|
|
'processing': {
|
|
'skip_sections': ['acknowledgments'],
|
|
'include_sections': ['preface', 'chapter'],
|
|
'clean_patterns': ['\\[\\d+\\]'],
|
|
'min_paragraph_length': 20
|
|
},
|
|
'output': {
|
|
'format': 'bilingual',
|
|
'filename_suffix': '_bilingual',
|
|
'preserve_images': True,
|
|
'preserve_css': True,
|
|
'output_dir': 'output'
|
|
},
|
|
'logging': {
|
|
'level': 'INFO',
|
|
'file': 'logs/test.log'
|
|
}
|
|
}
|
|
|
|
@pytest.fixture
|
|
def mock_translator(self, mock_config):
|
|
"""创建模拟翻译器"""
|
|
with patch('src.translator.OpenRouterClient') as mock_client:
|
|
mock_client.return_value.close = AsyncMock()
|
|
translator = EPUBTranslator(mock_config)
|
|
return translator
|
|
|
|
def test_translator_initialization(self, mock_translator):
|
|
"""测试翻译器初始化"""
|
|
assert mock_translator.config is not None
|
|
assert mock_translator.text_processor is not None
|
|
assert mock_translator.console is not None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_translation_estimate(self, mock_translator):
|
|
"""测试翻译估算"""
|
|
# 模拟 EPUBParser
|
|
with patch('src.translator.EPUBParser') as mock_parser:
|
|
mock_parser.return_value.extract_translatable_content.return_value = [
|
|
{
|
|
'title': 'Test Chapter',
|
|
'content': '<p>Test paragraph content</p>' * 10,
|
|
'type': 'chapter'
|
|
}
|
|
]
|
|
|
|
# 模拟 text_processor
|
|
mock_translator.text_processor.extract_paragraphs = Mock(return_value=[
|
|
{'text': 'Test paragraph content'} for _ in range(10)
|
|
])
|
|
|
|
estimate = await mock_translator.get_translation_estimate('test.epub')
|
|
|
|
assert 'total_paragraphs' in estimate
|
|
assert 'estimated_tokens' in estimate
|
|
assert 'estimated_time_minutes' in estimate
|
|
|
|
def test_get_translator_info(self, mock_translator):
|
|
"""测试获取翻译器信息"""
|
|
# 模拟 llm_client
|
|
mock_translator.llm_client.get_model_info = Mock(return_value={
|
|
'test_model': 'test_model',
|
|
'production_model': 'prod_model'
|
|
})
|
|
|
|
info = mock_translator.get_translator_info()
|
|
|
|
assert 'version' in info
|
|
assert 'llm_models' in info
|
|
assert 'config' in info
|
|
assert info['config']['chunk_size'] == 3
|
|
|
|
|
|
class TestOpenRouterClient:
|
|
"""OpenRouter 客户端测试类"""
|
|
|
|
@pytest.fixture
|
|
def mock_config(self):
|
|
"""模拟配置"""
|
|
return {
|
|
'openrouter': {
|
|
'api_key': 'test_key',
|
|
'base_url': 'https://openrouter.ai/api/v1',
|
|
'models': {
|
|
'test': 'google/gemini-2.0-flash-exp',
|
|
'production': 'google/gemini-exp-1206'
|
|
},
|
|
'rate_limits': {
|
|
'requests_per_minute': 60,
|
|
'concurrent_requests': 5
|
|
}
|
|
},
|
|
'translation': {
|
|
'temperature': 0.3,
|
|
'max_tokens': 4000
|
|
}
|
|
}
|
|
|
|
def test_client_initialization_invalid_key(self, mock_config):
|
|
"""测试无效 API Key"""
|
|
mock_config['openrouter']['api_key'] = 'YOUR_OPENROUTER_API_KEY'
|
|
|
|
with pytest.raises(ValueError, match="请在配置文件中设置有效的 OpenRouter API Key"):
|
|
OpenRouterClient(mock_config)
|
|
|
|
@patch('src.llm_client.AsyncOpenAI')
|
|
def test_client_initialization_valid(self, mock_openai, mock_config):
|
|
"""测试有效初始化"""
|
|
client = OpenRouterClient(mock_config)
|
|
|
|
assert client.models['test'] == 'google/gemini-2.0-flash-exp'
|
|
assert client.models['production'] == 'google/gemini-exp-1206'
|
|
mock_openai.assert_called_once()
|
|
|
|
@patch('src.llm_client.AsyncOpenAI')
|
|
def test_build_translation_prompt(self, mock_openai, mock_config):
|
|
"""测试翻译提示词构建"""
|
|
client = OpenRouterClient(mock_config)
|
|
|
|
prompt = client.build_translation_prompt(
|
|
"Hello world",
|
|
"This is a test book",
|
|
{"technical_terms": {"API": "应用程序接口"}}
|
|
)
|
|
|
|
assert "Hello world" in prompt
|
|
assert "This is a test book" in prompt
|
|
assert "API -> 应用程序接口" in prompt
|
|
|
|
@patch('src.llm_client.AsyncOpenAI')
|
|
def test_split_translation_result(self, mock_openai, mock_config):
|
|
"""测试翻译结果分割"""
|
|
client = OpenRouterClient(mock_config)
|
|
|
|
# 测试正常分割
|
|
translation = "第一段翻译\n\n第二段翻译\n\n第三段翻译"
|
|
result = client._split_translation_result(translation, 3)
|
|
|
|
assert len(result) == 3
|
|
assert result[0] == "第一段翻译"
|
|
assert result[1] == "第二段翻译"
|
|
assert result[2] == "第三段翻译"
|
|
|
|
# 测试单段落
|
|
single_translation = "单段落翻译"
|
|
result = client._split_translation_result(single_translation, 1)
|
|
|
|
assert len(result) == 1
|
|
assert result[0] == "单段落翻译" |