tests.core.test_engine_processor 源代码

"""
测试 engine_processor 模块
"""
import pytest
import pandas as pd
from unittest.mock import Mock, patch, MagicMock
from core.engine_processor import EngineProcessor
from core.base_sentence_generator import BaseSentenceGenerator
from core.param_translator import ParamTranslator
from core.config_manager import EngineConfig
from core.sentence_generator_manager import SentenceGeneratorManager


# 创建测试用的生成器类
[文档] class MockMusicGenerator(BaseSentenceGenerator): """测试音乐生成器""" param_config = { "Music": {"format": "play music {value}"}, "Sound": {"format": "play sound {value}"} } @property def category(self): return "music" @property def priority(self): return 10
[文档] def process(self, data): commands = [] if "Music" in data: commands.append(f"play music {data['Music']}") if "Sound" in data: commands.append(f"play sound {data['Sound']}") return commands if commands else None
[文档] class MockSceneGenerator(BaseSentenceGenerator): """测试场景生成器""" param_config = { "Background": {"format": "scene {value}"}, "Character": {"format": "show {value}"} } @property def category(self): return "scene" @property def priority(self): return 5
[文档] def process(self, data): commands = [] if "Background" in data: commands.append(f"scene {data['Background']}") if "Character" in data: commands.append(f"show {data['Character']}") return commands if commands else None
[文档] class MockDialogueGenerator(BaseSentenceGenerator): """测试对话生成器""" param_config = { "Speaker": {"format": "{value}"}, "Text": {"format": "\"{value}\""} } @property def category(self): return "dialogue" @property def priority(self): return 20
[文档] def process(self, data): if "Speaker" in data and "Text" in data: return [f"{data['Speaker']} \"{data['Text']}\""] return None
[文档] class BrokenGenerator(BaseSentenceGenerator): """会抛出异常的生成器""" param_config = { "Broken": {"format": "{value}"} } @property def category(self): return "broken" @property def priority(self): return 30
[文档] def process(self, data): raise ValueError("Intentional error for testing")
[文档] class TestEngineProcessor: """测试 EngineProcessor 类"""
[文档] @pytest.fixture def mock_translator(self): """创建模拟翻译器""" return Mock(spec=ParamTranslator)
[文档] @pytest.fixture def mock_config(self): """创建模拟配置""" config = Mock(spec=EngineConfig) config.engine_type = "test_engine" config.file_extension = ".test" return config
[文档] @pytest.fixture def mock_generator_manager(self): """创建模拟生成器管理器""" manager = Mock(spec=SentenceGeneratorManager) manager.load = Mock() return manager
[文档] @pytest.fixture def processor(self, mock_translator, mock_config): """创建处理器实例""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) return processor
[文档] def test_init(self, mock_translator, mock_config): """测试初始化""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) assert processor.engine_type == "test_engine" assert processor.translator == mock_translator assert processor.engine_config == mock_config assert processor.generators == [] assert processor.generator_param_map == {} mock_manager.load.assert_called_once()
[文档] def test_setup(self, processor, mock_translator, mock_config): """测试设置处理器""" # 创建模拟生成器实例 mock_gen1 = MockMusicGenerator(mock_translator, mock_config) mock_gen2 = MockSceneGenerator(mock_translator, mock_config) # 设置 generator_manager 的返回值 processor.generator_manager.create_generator_instances = Mock( return_value=[mock_gen1, mock_gen2] ) processor.setup() # 验证生成器已创建 assert len(processor.generators) == 2 assert processor.generators[0] == mock_gen1 assert processor.generators[1] == mock_gen2 # 验证参数映射已构建 assert len(processor.generator_param_map) == 2 assert mock_gen1 in processor.generator_param_map assert mock_gen2 in processor.generator_param_map
[文档] def test_build_generator_param_map(self, processor, mock_translator, mock_config): """测试构建生成器参数映射""" # 创建生成器 gen1 = MockMusicGenerator(mock_translator, mock_config) gen2 = MockSceneGenerator(mock_translator, mock_config) processor.generators = [gen1, gen2] param_map = processor._build_generator_param_map() # 验证映射 assert gen1 in param_map assert gen2 in param_map assert "Music" in param_map[gen1] assert "Sound" in param_map[gen1] assert "Background" in param_map[gen2] assert "Character" in param_map[gen2]
[文档] def test_build_generator_param_map_no_param_config(self, processor, mock_translator, mock_config): """测试构建参数映射(生成器没有 param_config)""" # 创建一个没有 param_config 的生成器 class NoConfigGenerator(BaseSentenceGenerator): @property def category(self): return "no_config" def process(self, data): return [] gen = NoConfigGenerator(mock_translator, mock_config) processor.generators = [gen] param_map = processor._build_generator_param_map() # 应该有映射,但参数列表为空 assert gen in param_map assert param_map[gen] == []
[文档] def test_build_generator_param_map_none_param_config(self, processor, mock_translator, mock_config): """测试构建参数映射(param_config 为 None)""" # 创建一个 param_config 为 None 的生成器 class NoneConfigGenerator(BaseSentenceGenerator): param_config = None @property def category(self): return "none_config" def process(self, data): return [] gen = NoneConfigGenerator(mock_translator, mock_config) processor.generators = [gen] param_map = processor._build_generator_param_map() # 应该有映射,但参数列表为空 assert gen in param_map assert param_map[gen] == []
[文档] class TestProcessRow: """测试 process_row 方法"""
[文档] @pytest.fixture def mock_translator(self): """创建模拟翻译器""" return Mock(spec=ParamTranslator)
[文档] @pytest.fixture def mock_config(self): """创建模拟配置""" config = Mock(spec=EngineConfig) config.engine_type = "test_engine" return config
[文档] @pytest.fixture def processor_with_generators(self, mock_translator, mock_config): """创建带有生成器的处理器""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) # 手动设置生成器 gen1 = MockSceneGenerator(mock_translator, mock_config) gen2 = MockMusicGenerator(mock_translator, mock_config) gen3 = MockDialogueGenerator(mock_translator, mock_config) processor.generators = [gen1, gen2, gen3] processor.generator_param_map = processor._build_generator_param_map() return processor
[文档] def test_process_row_with_all_params(self, processor_with_generators): """测试处理包含所有参数的行""" row_data = pd.Series({ "Background": "bg_room", "Character": "alice", "Music": "bgm_main", "Sound": "sfx_door", "Speaker": "Alice", "Text": "Hello!" }) results = processor_with_generators.process_row(row_data) # 验证所有生成器都生成了命令 assert len(results) > 0 assert "scene bg_room" in results assert "show alice" in results assert "play music bgm_main" in results assert "play sound sfx_door" in results assert "Alice \"Hello!\"" in results
[文档] def test_process_row_with_partial_params(self, processor_with_generators): """测试处理部分参数的行""" row_data = pd.Series({ "Background": "bg_room", "Music": "bgm_main" }) results = processor_with_generators.process_row(row_data) # 只有相关生成器生成命令 assert len(results) == 2 assert "scene bg_room" in results assert "play music bgm_main" in results
[文档] def test_process_row_with_empty_values(self, processor_with_generators): """测试处理包含空值的行""" row_data = pd.Series({ "Background": "bg_room", "Character": "", # 空字符串 "Music": None, # None "Speaker": "Alice", "Text": "" # 空字符串 }) results = processor_with_generators.process_row(row_data) # 只有非空值生成命令 assert len(results) == 1 assert "scene bg_room" in results
[文档] def test_process_row_with_nan_values(self, processor_with_generators): """测试处理包含 NaN 值的行""" row_data = pd.Series({ "Background": "bg_room", "Character": pd.NA, # pandas NA "Music": float('nan'), # NaN "Sound": "sfx_door" }) results = processor_with_generators.process_row(row_data) # NaN 值应该被过滤 assert "scene bg_room" in results assert "play sound sfx_door" in results assert len(results) == 2
[文档] def test_process_row_with_no_matching_params(self, processor_with_generators): """测试处理没有匹配参数的行""" row_data = pd.Series({ "UnknownParam1": "value1", "UnknownParam2": "value2" }) results = processor_with_generators.process_row(row_data) # 没有生成器能处理这些参数 assert results == []
[文档] def test_process_row_with_generator_error(self, mock_translator, mock_config): """测试生成器抛出异常时的处理""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) # 添加一个正常生成器和一个会出错的生成器 gen1 = MockMusicGenerator(mock_translator, mock_config) gen2 = BrokenGenerator(mock_translator, mock_config) processor.generators = [gen1, gen2] processor.generator_param_map = processor._build_generator_param_map() row_data = pd.Series({ "Music": "bgm_main", "Broken": "test" }) # 应该捕获异常并继续处理 results = processor.process_row(row_data) # 正常生成器应该生成命令 assert "play music bgm_main" in results # 出错的生成器不应该影响结果 assert len(results) == 1
[文档] def test_process_row_empty_row(self, processor_with_generators): """测试处理空行""" row_data = pd.Series({}) results = processor_with_generators.process_row(row_data) assert results == []
[文档] @pytest.mark.parametrize("return_value,category_name", [ (None, "none"), ([], "empty"), ]) def test_process_row_generator_returns_empty(self, mock_translator, mock_config, return_value, category_name): """测试生成器返回 None 或空列表的情况""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) # 创建一个返回指定值的生成器 class TestGenerator(BaseSentenceGenerator): param_config = {"Test": {}} @property def category(self): return category_name def process(self, data): return return_value gen = TestGenerator(mock_translator, mock_config) processor.generators = [gen] processor.generator_param_map = processor._build_generator_param_map() row_data = pd.Series({"Test": "value"}) results = processor.process_row(row_data) # 返回 None 或空列表的生成器不应该添加到结果中 assert results == []
[文档] class TestPipelineInfo: """测试管道信息相关方法"""
[文档] @pytest.fixture def mock_translator(self): """创建模拟翻译器""" return Mock(spec=ParamTranslator)
[文档] @pytest.fixture def mock_config(self): """创建模拟配置""" config = Mock(spec=EngineConfig) config.engine_type = "test_engine" return config
[文档] @pytest.fixture def processor_with_generators(self, mock_translator, mock_config): """创建带有生成器的处理器""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) # 按优先级顺序添加生成器 gen1 = MockSceneGenerator(mock_translator, mock_config) # priority=5 gen2 = MockMusicGenerator(mock_translator, mock_config) # priority=10 gen3 = MockDialogueGenerator(mock_translator, mock_config) # priority=20 processor.generators = [gen1, gen2, gen3] processor.generator_param_map = processor._build_generator_param_map() return processor
[文档] def test_get_pipeline_info(self, processor_with_generators): """测试获取管道信息""" info = processor_with_generators.get_pipeline_info() # 验证基本信息 assert "total_stages" in info assert "pipeline" in info assert info["total_stages"] == 3 # 验证管道详情 pipeline = info["pipeline"] assert len(pipeline) == 3 # 验证第一个阶段 assert pipeline[0]["stage"] == 1 assert pipeline[0]["name"] == "MockSceneGenerator" assert pipeline[0]["type"] == "scene" assert pipeline[0]["priority"] == 5 # 验证第二个阶段 assert pipeline[1]["stage"] == 2 assert pipeline[1]["name"] == "MockMusicGenerator" assert pipeline[1]["type"] == "music" assert pipeline[1]["priority"] == 10 # 验证第三个阶段 assert pipeline[2]["stage"] == 3 assert pipeline[2]["name"] == "MockDialogueGenerator" assert pipeline[2]["type"] == "dialogue" assert pipeline[2]["priority"] == 20
[文档] def test_get_pipeline_info_empty(self, mock_translator, mock_config): """测试获取空管道信息""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) processor.generators = [] info = processor.get_pipeline_info() assert info["total_stages"] == 0 assert info["pipeline"] == []
[文档] def test_get_pipeline_info_with_category(self, mock_translator, mock_config): """测试获取管道信息(验证 category 正确获取)""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) # 创建一个有 category 的生成器 gen = MockMusicGenerator(mock_translator, mock_config) processor.generators = [gen] info = processor.get_pipeline_info() # 验证 category 被正确获取 assert info["pipeline"][0]["type"] == "music" assert info["pipeline"][0]["name"] == "MockMusicGenerator"
[文档] def test_get_generator_manager(self, processor_with_generators): """测试获取生成器管理器""" manager = processor_with_generators.get_generator_manager() assert manager is not None assert isinstance(manager, (SentenceGeneratorManager, Mock))
[文档] class TestEngineProcessorIntegration: """集成测试:测试完整的工作流程"""
[文档] @pytest.fixture def mock_translator(self): """创建模拟翻译器""" return Mock(spec=ParamTranslator)
[文档] @pytest.fixture def mock_config(self): """创建模拟配置""" config = Mock(spec=EngineConfig) config.engine_type = "test_engine" return config
[文档] def test_full_workflow(self, mock_translator, mock_config): """测试完整工作流程""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() # 创建生成器实例 gen1 = MockSceneGenerator(mock_translator, mock_config) gen2 = MockMusicGenerator(mock_translator, mock_config) gen3 = MockDialogueGenerator(mock_translator, mock_config) mock_manager.create_generator_instances = Mock( return_value=[gen1, gen2, gen3] ) MockManager.return_value = mock_manager # 1. 初始化处理器 processor = EngineProcessor("test_engine", mock_translator, mock_config) assert processor.engine_type == "test_engine" # 2. 设置处理器 processor.setup() assert len(processor.generators) == 3 # 3. 获取管道信息 info = processor.get_pipeline_info() assert info["total_stages"] == 3 # 4. 处理多行数据 rows = [ pd.Series({"Background": "bg1", "Music": "bgm1"}), pd.Series({"Character": "alice", "Speaker": "Alice", "Text": "Hi"}), pd.Series({"Sound": "sfx1"}) ] all_results = [] for row in rows: results = processor.process_row(row) all_results.extend(results) # 验证所有命令都生成了 assert len(all_results) > 0 assert any("scene bg1" in cmd for cmd in all_results) assert any("play music bgm1" in cmd for cmd in all_results) assert any("show alice" in cmd for cmd in all_results) assert any("Alice \"Hi\"" in cmd for cmd in all_results) assert any("play sound sfx1" in cmd for cmd in all_results)
[文档] def test_multiple_rows_processing(self, mock_translator, mock_config): """测试处理多行数据""" with patch('core.engine_processor.SentenceGeneratorManager') as MockManager: mock_manager = Mock(spec=SentenceGeneratorManager) mock_manager.load = Mock() gen = MockMusicGenerator(mock_translator, mock_config) mock_manager.create_generator_instances = Mock(return_value=[gen]) MockManager.return_value = mock_manager processor = EngineProcessor("test_engine", mock_translator, mock_config) processor.setup() # 处理多行 rows = [ pd.Series({"Music": "bgm1"}), pd.Series({"Music": "bgm2"}), pd.Series({"Sound": "sfx1"}), ] results = [processor.process_row(row) for row in rows] # 验证每行都被正确处理 assert len(results) == 3 assert "play music bgm1" in results[0] assert "play music bgm2" in results[1] assert "play sound sfx1" in results[2]