From 1c1c88e2cf396ee8b4111d94718b12c90c5ac49c Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Mon, 23 Mar 2026 23:02:12 +0800 Subject: [PATCH] test(15-00): add test infrastructure placeholders for Phase 15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创建了测试基础设施,为后续计划提供测试框架。 ## 创建的文件 1. **tests/test_processors/test_srt.py** - SRTProcessor 配置集成测试占位符 - TestConfigIntegration 测试类 - 5 个测试占位符(pytest.skip) 2. **tests/test_backward_compat.py** - 向后兼容性测试 - TestBackwardCompatibility 测试类 - 部分测试已实现(来自计划 15-01) - 部分测试占位符(pytest.skip) 3. **tests/conftest.py** - 添加共享 fixtures - temp_env_vars fixture:临时环境变量管理 - mock_kbsettings fixture:Mock KBSettings 实例 - 添加 os 模块导入 ## 偏离说明 计划要求所有测试都是占位符(pytest.skip),但实际上: - test_config.py 已由计划 15-01 实现完整测试 - test_backward_compat.py 有部分实现 原因:计划 15-01 在 15-00 之前执行,导致测试不是占位符。 ## 验证 - pytest 发现所有新增测试类和测试函数 - 测试文件结构符合项目规范 Co-Authored-By: Claude Opus 4.6 --- tests/conftest.py | 52 +++++++++ tests/test_backward_compat.py | 179 ++++++++++++++++++++++++++++++ tests/test_processors/test_srt.py | 35 ++++++ 3 files changed, 266 insertions(+) create mode 100644 tests/test_backward_compat.py create mode 100644 tests/test_processors/test_srt.py diff --git a/tests/conftest.py b/tests/conftest.py index 972511c..e33e481 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ """共享测试 fixtures 和配置""" +import os from typing import Optional from unittest.mock import Mock from pathlib import Path @@ -182,3 +183,54 @@ def long_subtitle_segments(): text=f"这是第 {i + 1} 句字幕内容。" * 5 )) return segments + + +# ============================================================================ +# Phase 15 Fixtures: 配置集成测试 +# ============================================================================ + +@pytest.fixture +def temp_env_vars(monkeypatch): + """临时环境变量 fixture,测试后自动清理""" + original_env = {} + saved_vars = {} + + def _set_env(env_dict): + nonlocal original_env, saved_vars + # 保存原始值 + for key in env_dict.keys(): + original_env[key] = os.environ.get(key) + # 设置新值 + for key, value in env_dict.items(): + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value + saved_vars[key] = value + + yield _set_env + + # 清理:恢复原始环境变量 + for key, original_value in original_env.items(): + if original_value is None: + os.environ.pop(key, None) + else: + os.environ[key] = original_value + + +@pytest.fixture +def mock_kbsettings(): + """Mock KBSettings fixture,用于隔离测试环境""" + from kb_workflow.pipeline.config import KBSettings + from kb_workflow.pipeline.config import get_settings + + mock_settings = MagicMock(spec=KBSettings) + mock_settings.enable_semantic_segmentation = True + mock_settings.base_path = "/tmp/kb" + mock_settings.archive_path = "/tmp/archive" + mock_settings.hf_token = MagicMock() + mock_settings.hf_token.get_secret_value.return_value = "" + mock_settings.zhihu_cookie = MagicMock() + mock_settings.zhihu_cookie.get_secret_value.return_value = "" + + return mock_settings diff --git a/tests/test_backward_compat.py b/tests/test_backward_compat.py new file mode 100644 index 0000000..c3ca01e --- /dev/null +++ b/tests/test_backward_compat.py @@ -0,0 +1,179 @@ +"""Backward Compatibility Tests + +Tests to ensure that configuration changes don't break existing functionality: +- Rule-based segmentation (SemanticSegmenter) works unchanged (CONFIG-02) +- AnalyzeProcessor is not affected by SRTProcessor config changes (CONFIG-03) +- Existing API calls remain compatible +""" + +import pytest +from unittest.mock import Mock, patch, MagicMock +from pathlib import Path +from kb_workflow.srt.parser import Segment +from kb_workflow.srt.segmenter import SemanticSegmenter + + +# ============================================================================= +# Test Fixtures +# ============================================================================= + +@pytest.fixture +def sample_segments(): + """Sample subtitle segments for testing""" + return [ + Segment(1, 0.0, 5.0, "第一句内容"), + Segment(2, 5.0, 10.0, "第二句内容"), + Segment(3, 10.0, 15.0, "第三句内容"), + ] + + +@pytest.fixture +def long_text_segments(): + """Segments with long text that triggers length-based splitting""" + text = "这是一段很长的文本内容" * 50 # ~500 characters + return [ + Segment(1, 0.0, 5.0, text[:250]), + Segment(2, 5.0, 10.0, text[:250]), + Segment(3, 10.0, 15.0, text[:250]), + ] + + +@pytest.fixture +def gap_segments(): + """Segments with time gaps > 3 seconds""" + return [ + Segment(1, 0.0, 5.0, "第一段落内容"), + Segment(2, 10.0, 15.0, "第二段落内容"), # 5 second gap + Segment(3, 15.0, 20.0, "继续第二段落"), + ] + + +# ============================================================================= +# Task 1: Rule-based Segmentation Backward Compatibility (CONFIG-02) +# ============================================================================= + +class TestBackwardCompatibility: + """Test backward compatibility for rule-based segmentation (CONFIG-02)""" + + def test_rule_based_segmentation_unchanged(self, sample_segments): + """Test 1: enable_semantic_segmentation=False 使用规则分段器""" + from kb_workflow.pipeline.processors.srt import SRTProcessor + from kb_workflow.srt.semantic_engine import SemanticSegmentationEngine + + # Create SRTProcessor with semantic segmentation disabled + processor = SRTProcessor( + state_manager=None, + enable_semantic_segmentation=False + ) + + # Verify it uses SemanticSegmenter (rule-based) + assert isinstance(processor.segmenter, SemanticSegmenter), \ + "Should use SemanticSegmenter when enable_semantic_segmentation=False" + + # Verify it does NOT use SemanticSegmentationEngine + assert not isinstance(processor.segmenter, SemanticSegmentationEngine), \ + "Should NOT use SemanticSegmentationEngine when enable_semantic_segmentation=False" + + # Verify segment() returns plain text list (not VideoSegment objects) + result = processor.segmenter.segment(sample_segments) + assert isinstance(result, list), "segment() should return a list" + assert len(result) > 0, "Should have at least one paragraph" + assert all(isinstance(para, str) for para in result), \ + "Rule-based segmenter should return plain text strings, not VideoSegment objects" + + def test_rule_based_no_llm_calls(self, sample_segments): + """Test 2: 规则分段不调用 LLM,无网络开销""" + from kb_workflow.pipeline.processors.srt import SRTProcessor + + # Mock SimpleAIClient to detect any LLM calls + with patch('kb_workflow.deep_analyzer.SimpleAIClient') as mock_client: + # Create processor with rule-based segmentation + processor = SRTProcessor( + state_manager=None, + enable_semantic_segmentation=False + ) + + # Call segment method + result = processor.segmenter.segment(sample_segments) + + # Verify SimpleAIClient was NOT called + # (rule-based segmentation should not initialize AI client) + assert not mock_client.called, \ + "Rule-based segmentation should NOT call SimpleAIClient" + + # Verify we still get results without LLM + assert len(result) > 0, "Should have results without LLM" + + def test_rule_based_thresholds_unchanged(self, gap_segments, long_text_segments): + """Test 3: 规则分段的分段逻辑(时间/长度阈值)未改变""" + segmenter = SemanticSegmenter( + gap_threshold=3.0, # Phase 12 default + max_paragraph_chars=500 # Phase 12 default + ) + + # Test 1: Time gap threshold (3 seconds) + result = segmenter.segment(gap_segments) + # Should split into 2 paragraphs due to 5-second gap + assert len(result) == 2, \ + f"Should split into 2 paragraphs due to time gap, got {len(result)}" + assert "第一段落内容" in result[0] + assert "第二段落内容" in result[1] + + # Test 2: Character length threshold (500 chars) + result = segmenter.segment(long_text_segments) + # Each segment is ~250 chars, so with 3 segments we should get splits + # First segment: 250 chars (no split) + # Second segment: 250 + 250 = 500 chars (might split depending on exact count) + assert len(result) >= 1, "Should have at least one paragraph" + assert all(isinstance(para, str) for para in result), \ + "All results should be plain text strings" + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 3") + def test_existing_api_compatibility(self): + """Test 4: 不传 enable_semantic_segmentation 参数时使用配置默认值""" + # This test will be implemented in Task 3 + pass + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 3") + def test_explicit_false_overrides_config(self): + """Test 5: 显式传 False 时覆盖配置,使用规则分段""" + # This test will be implemented in Task 3 + pass + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 3") + def test_migration_path(self): + """Test 6: 验证用户可以通过两种方式启用语义分段""" + # This test will be implemented in Task 3 + pass + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 2") + def test_analyze_processor_unaffected(self): + """Test 7: AnalyzeProcessor 不受 KBSettings.enable_semantic_segmentation 影响""" + # This test will be implemented in Task 2 + pass + + +# ============================================================================= +# Task 2: AnalyzeProcessor Independence (CONFIG-03) +# ============================================================================= + +class TestAnalyzeProcessorIndependence: + """Test that AnalyzeProcessor is independent of SRTProcessor configuration""" + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 2") + def test_analyze_processor_unaffected_by_config(self): + """Test: AnalyzeProcessor 不受 KBSettings.enable_semantic_segmentation 影响""" + # This test will be implemented in Task 2 + pass + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 2") + def test_analyze_processor_normal_initialization(self): + """Test: AnalyzeProcessor 正常初始化和处理""" + # This test will be implemented in Task 2 + pass + + @pytest.mark.skipif(True, reason="Not implemented yet - will implement in Task 2") + def test_analyze_processor_output_unchanged(self): + """Test: AnalyzeProcessor 输出格式未改变""" + # This test will be implemented in Task 2 + pass diff --git a/tests/test_processors/test_srt.py b/tests/test_processors/test_srt.py new file mode 100644 index 0000000..7595f2f --- /dev/null +++ b/tests/test_processors/test_srt.py @@ -0,0 +1,35 @@ +"""Tests for SRTProcessor and configuration integration. + +Test coverage: +- Configuration integration (CONFIG-01) +- Backward compatibility (CONFIG-02) +- Output format verification (OUTPUT-01, OUTPUT-02, OUTPUT-03) +""" + +import pytest +from unittest.mock import MagicMock, patch +from pathlib import Path + + +class TestConfigIntegration: + """Tests for SRTProcessor configuration integration (CONFIG-01, CONFIG-02)""" + + def test_srt_processor_reads_from_kbsettings(self): + """Test SRTProcessor reads enable_semantic_segmentation from KBSettings""" + pytest.skip("等待实现: 验证从 KBSettings 读取配置") + + def test_srt_processor_constructor_overrides_config(self): + """Test constructor parameter overrides KBSettings""" + pytest.skip("等待实现: 验证构造函数参数优先级") + + def test_config_disabled_uses_rule_based(self): + """Test disabled config uses rule-based segmentation (CONFIG-02)""" + pytest.skip("等待实现: 验证禁用时使用规则分段") + + def test_output_format_with_semantic_enabled(self): + """Test output format with semantic segmentation enabled (OUTPUT-01)""" + pytest.skip("等待实现: 验证启用时输出 H2 标题") + + def test_output_format_with_semantic_disabled(self): + """Test output format with semantic segmentation disabled (OUTPUT-03)""" + pytest.skip("等待实现: 验证禁用时输出\"## 内容\"")