Skip to main content
Glama
leeguooooo
by leeguooooo
TEST_IMPROVEMENT_PLAN.md11.7 kB
# 测试改进计划 ## 🚨 当前问题分析 ### 本次修复的关键 Bug(未被测试捕获) | Bug | 严重性 | 影响 | 为什么测试没发现 | |-----|--------|------|------------------| | 1. UID vs 序列号混乱 | 🔴 Critical | 删除/标记错误的邮件 | 没有 mock IMAP 响应测试 | | 2. `account_id` 路由错误 | 🔴 Critical | 跨账户数据泄漏 | 缺少集成测试 | | 3. 连接泄漏 | 🔴 Critical | 资源耗尽 | 没有连接管理测试 | | 4. FLAGS 解析错误 | 🟡 High | 功能失败 | 没有 IMAP 协议测试 | | 5. 缓存空列表误判 | 🟡 High | 性能下降 100-200x | 没有缓存逻辑测试 | | 6. 多账户缓存逻辑错误 | 🔴 Critical | 数据不完整 | 没有多账户场景测试 | | 7. 文件夹名称未引用 | 🟡 High | IMAP BAD 请求 | 没有协议兼容性测试 | ### 现有测试覆盖情况 #### ✅ 已覆盖(约 30%) - Schema 定义验证 - 参数类型检查 - AccountManager 基础功能 - 并行操作线程安全(部分) #### ❌ 未覆盖(约 70%) - **IMAP 操作逻辑**:UID 处理、FLAGS 解析、连接管理 - **缓存系统**:缓存命中/失效判断、数据一致性 - **多账户隔离**:跨账户场景、账户路由 - **协议兼容性**:文件夹名称编码、特殊字符处理 - **错误处理**:边界条件、None 值、空列表 - **性能优化**:header-only fetch、body truncation --- ## 📋 测试改进方案 ### 阶段 1:核心 IMAP 操作测试(优先级:🔴 最高) #### 1.1 创建 `test_imap_operations.py` ```python """ 测试核心 IMAP 操作逻辑(使用 Mock) """ import unittest from unittest.mock import Mock, patch, MagicMock from src.legacy_operations import ( fetch_emails, get_email_detail, mark_email_read, delete_email, move_email_to_trash, batch_mark_read ) class TestUIDHandling(unittest.TestCase): """测试 UID vs 序列号处理""" def test_fetch_emails_returns_uid(self): """测试 fetch_emails 返回稳定的 UID 而非序列号""" # Mock IMAP 响应 # ... 实现 def test_get_email_detail_uid_fallback(self): """测试 get_email_detail UID 失败时回退到序列号""" # ... 实现 def test_mark_email_read_uses_uid_first(self): """测试 mark_email_read 优先使用 UID""" # ... 实现 class TestFLAGSParsing(unittest.TestCase): """测试 FLAGS 解析""" def test_parse_flags_from_tuple_response(self): """测试从 IMAP tuple 响应解析 FLAGS""" # Mock: (b'1 (UID 123 FLAGS (\\Seen))', b'') # ... 实现 def test_parse_flags_with_multiple_flags(self): """测试解析多个 FLAGS""" # Mock: (b'1 (FLAGS (\\Seen \\Flagged \\Answered))', b'') # ... 实现 class TestConnectionManagement(unittest.TestCase): """测试连接管理(防止泄漏)""" def test_fetch_emails_always_closes_connection(self): """测试 fetch_emails 总是关闭连接(即使异常)""" # ... 实现 def test_connection_closed_on_error(self): """测试出错时连接仍被关闭""" # ... 实现 ``` #### 1.2 创建 `test_folder_normalization.py` ```python """ 测试文件夹名称规范化 """ import unittest from src.legacy_operations import _normalize_folder_name class TestFolderNormalization(unittest.TestCase): """测试文件夹名称规范化""" def test_inbox_unchanged(self): """测试 INBOX 不变""" self.assertEqual(_normalize_folder_name('INBOX'), 'INBOX') def test_spaces_quoted(self): """测试包含空格的文件夹名称被引用""" result = _normalize_folder_name('Deleted Messages') self.assertEqual(result, '"Deleted Messages"') def test_utf7_encoding(self): """测试非 ASCII 字符使用 UTF-7 编码""" result = _normalize_folder_name('草稿箱') # 验证结果是有效的 IMAP UTF-7 self.assertIsInstance(result, (str, bytes)) def test_empty_defaults_to_inbox(self): """测试空字符串默认为 INBOX""" self.assertEqual(_normalize_folder_name(''), 'INBOX') self.assertEqual(_normalize_folder_name(None), 'INBOX') ``` --- ### 阶段 2:缓存系统测试(优先级:🟡 高) #### 2.1 创建 `test_cache_logic.py` ```python """ 测试缓存逻辑 """ import unittest from unittest.mock import Mock, patch from src.legacy_operations import fetch_emails from src.operations.cached_operations import CachedEmailOperations class TestCacheHitLogic(unittest.TestCase): """测试缓存命中逻辑""" def test_cache_returns_empty_list_still_valid(self): """关键测试:缓存返回空列表时仍被视为有效""" # Mock: cache.list_emails_cached 返回 {"emails": [], "from_cache": True} # 验证:不应回退到 IMAP # ... 实现 def test_cache_returns_none_triggers_imap(self): """测试缓存返回 None 时触发 IMAP""" # ... 实现 def test_cache_age_validation(self): """测试缓存年龄验证(< 15 分钟)""" # ... 实现 class TestMultiAccountCacheLogic(unittest.TestCase): """测试多账户缓存逻辑""" def test_cache_skipped_for_multi_account(self): """关键测试:多账户请求跳过缓存""" # Mock: account_id=None, use_cache=True # 验证:不调用 cache.list_emails_cached # ... 实现 def test_cache_used_for_single_account(self): """测试单账户请求使用缓存""" # Mock: account_id='env_163', use_cache=True # 验证:调用 cache.list_emails_cached # ... 实现 ``` --- ### 阶段 3:多账户隔离测试(优先级:🔴 最高) #### 3.1 扩展 `test_account_manager.py` ```python class TestAccountIDRouting(unittest.TestCase): """测试 account_id 路由""" def test_fetch_emails_returns_canonical_account_id(self): """测试 fetch_emails 返回规范的 account_id(非 email)""" # 验证:返回 'env_163' 而非 'leeguoo@163.com' # ... 实现 def test_search_emails_preserves_account_id(self): """测试 search_emails 保留正确的 account_id""" # ... 实现 def test_get_email_detail_uses_canonical_account_id(self): """测试 get_email_detail 使用规范的 account_id""" # ... 实现 def test_account_lookup_by_email_fallback(self): """测试通过 email 查找账户的回退逻辑""" # ... 实现 ``` --- ### 阶段 4:性能优化测试(优先级:🟢 中) #### 4.1 创建 `test_performance_optimizations.py` ```python """ 测试性能优化 """ import unittest from unittest.mock import Mock, patch class TestHeaderOnlyFetch(unittest.TestCase): """测试仅获取头部""" def test_fetch_emails_uses_header_only(self): """测试 fetch_emails 仅获取头部(不获取 body)""" # Mock IMAP fetch # 验证:使用 BODY.PEEK[HEADER.FIELDS (...)] # ... 实现 class TestBodyTruncation(unittest.TestCase): """测试 body 截断""" def test_get_email_detail_truncates_large_body(self): """测试 get_email_detail 截断大 body(> 100KB)""" # ... 实现 def test_html_to_text_conversion(self): """测试 HTML 转纯文本""" # ... 实现 ``` --- ## 🔧 实施步骤 ### 第 1 步:修复当前测试环境 ```bash # 安装测试依赖 uv pip install pytest pytest-cov pytest-mock # 验证现有测试可运行 uv run pytest tests/ -v ``` ### 第 2 步:创建 Mock IMAP 响应库 创建 `tests/fixtures/imap_responses.py`: ```python """ 常见 IMAP 响应的 Mock 数据 """ # UID FETCH 响应 UID_FETCH_RESPONSE = ( b'1 (UID 12345 FLAGS (\\Seen) RFC822.SIZE 2048)', b'' ) # FLAGS 解析测试数据 FLAGS_RESPONSES = { 'single_flag': (b'1 (FLAGS (\\Seen))', b''), 'multiple_flags': (b'1 (FLAGS (\\Seen \\Flagged))', b''), 'with_uid': (b'1 (UID 123 FLAGS (\\Seen))', b''), } # 文件夹列表响应 FOLDER_LIST_RESPONSE = [ b'(\\HasNoChildren) "/" "INBOX"', b'(\\HasNoChildren) "/" "Sent Items"', b'(\\HasNoChildren) "/" "Deleted Messages"', ] ``` ### 第 3 步:逐步实现测试 按优先级顺序: 1. **Week 1**: 核心 IMAP 操作测试(阶段 1) 2. **Week 2**: 缓存系统测试(阶段 2) 3. **Week 3**: 多账户隔离测试(阶段 3) 4. **Week 4**: 性能优化测试(阶段 4) ### 第 4 步:集成到 CI/CD ```yaml # .github/workflows/test.yml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run tests run: | uv run pytest tests/ --cov=src --cov-report=xml - name: Upload coverage uses: codecov/codecov-action@v2 ``` --- ## 📊 测试覆盖率目标 | 模块 | 当前覆盖率 | 目标覆盖率 | |------|-----------|-----------| | `legacy_operations.py` | ~10% | **80%** | | `cached_operations.py` | 0% | **70%** | | `account_manager.py` | ~60% | **90%** | | `connection_manager.py` | 0% | **70%** | | **整体** | **~30%** | **75%** | --- ## 🎯 成功标准 ### 短期目标(1 个月内) - ✅ 所有已修复的 Bug 都有对应的回归测试 - ✅ 核心 IMAP 操作测试覆盖率达到 70% - ✅ CI/CD 集成完成 ### 中期目标(3 个月内) - ✅ 测试覆盖率达到 75% - ✅ 每次 PR 必须包含测试 - ✅ 测试运行时间 < 30 秒 ### 长期目标(6 个月内) - ✅ 集成测试(真实 IMAP 服务器) - ✅ 性能基准测试 - ✅ 模糊测试(fuzzing) --- ## 💡 测试最佳实践 ### 1. 使用 AAA 模式 ```python def test_example(self): # Arrange(准备) mock_imap = Mock() mock_imap.fetch.return_value = ('OK', [MOCK_DATA]) # Act(执行) result = fetch_emails(limit=10) # Assert(断言) self.assertEqual(len(result['emails']), 10) mock_imap.fetch.assert_called_once() ``` ### 2. 使用有意义的测试名称 ```python # ❌ Bad def test_fetch(self): ... # ✅ Good def test_fetch_emails_returns_uid_not_sequence_number(self): ... ``` ### 3. 每个测试只测一件事 ```python # ❌ Bad: 测试多个场景 def test_everything(self): test_normal_case() test_error_case() test_edge_case() # ✅ Good: 分开测试 def test_normal_case(self): ... def test_error_case(self): ... def test_edge_case(self): ... ``` ### 4. 使用 pytest fixtures ```python @pytest.fixture def mock_imap_connection(): """提供一个 mock IMAP 连接""" mock = Mock() mock.select.return_value = ('OK', [b'10']) return mock def test_with_fixture(mock_imap_connection): # 使用 fixture result = fetch_emails(connection=mock_imap_connection) ... ``` --- ## 🔍 建议的下一步 1. **立即行动**(本周): - ✅ 修复现有测试的语法错误 - ✅ 为本次修复的 7 个 Bug 编写回归测试 - ✅ 设置测试覆盖率报告 2. **短期行动**(本月): - 实现阶段 1 和阶段 3 的测试 - 达到 50% 测试覆盖率 - 集成 CI/CD 3. **中期行动**(3 个月内): - 完成所有 4 个阶段的测试 - 达到 75% 测试覆盖率 - 建立测试文化(PR 必须包含测试) --- **结论**:当前测试主要关注"定义正确性"而非"行为正确性"。需要大幅扩展测试覆盖,特别是核心 IMAP 操作、缓存逻辑、多账户隔离这三个关键领域。

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/leeguooooo/email-mcp-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server