test(15-00): add test infrastructure placeholders for Phase 15

创建了测试基础设施,为后续计划提供测试框架。

## 创建的文件

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 23:02:12 +08:00
parent 8303cea018
commit 1c1c88e2cf
3 changed files with 266 additions and 0 deletions
+52
View File
@@ -1,5 +1,6 @@
"""共享测试 fixtures 和配置""" """共享测试 fixtures 和配置"""
import os
from typing import Optional from typing import Optional
from unittest.mock import Mock from unittest.mock import Mock
from pathlib import Path from pathlib import Path
@@ -182,3 +183,54 @@ def long_subtitle_segments():
text=f"这是第 {i + 1} 句字幕内容。" * 5 text=f"这是第 {i + 1} 句字幕内容。" * 5
)) ))
return segments 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
+179
View File
@@ -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
+35
View File
@@ -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("等待实现: 验证禁用时输出\"## 内容\"")