Files
hot_comment_radar/docs/TDD.md
T

28 KiB
Raw Blame History

TDD.md:小红书 / 抖音热榜评论抓取 + AI 分析报告工具

1. 文档信息

  • 文档阶段:TDDTest-Driven Development,测试驱动开发计划)
  • 需求依据:docs/PRD.mddocs/FeatureSummary.md
  • 技术依据:docs/DevelopmentPlan.md
  • UI 依据:docs/UIDesign.md
  • 当前目标:定义 MVP 开发前必须先写的测试范围、测试顺序、Mock 策略、验收命令与端到端测试清单
  • 测试原则:先写失败测试,再写最小实现;任何业务代码变更前必须先有对应测试

2. TDD 总原则

2.1 红绿重构流程

每个功能点按以下顺序执行:

  1. Red:先写一个最小失败测试。
  2. Verify Red:运行测试,确认失败原因是目标功能缺失,而不是测试代码错误。
  3. Green:写最小实现让测试通过。
  4. Verify Green:运行该测试和相关测试,确认通过。
  5. Refactor:只在测试通过后清理代码。

禁止事项:

  • 禁止先实现后补测试。
  • 禁止为了让测试通过而删除关键断言。
  • 禁止用真实 TikHub / AI API 作为单元测试依赖。
  • 禁止在模板中硬编码中文状态文案,状态展示必须通过映射或 Macro 测试覆盖。

2.2 测试分层

层级 工具 目标
单元测试 pytest 字段映射、配置校验、分页、统计、导出、AI schema
服务集成测试 pytest + TestClient + mock httpx 任务创建、后台流程、平台链路、报告生成
模板测试 pytest + Jinja2 渲染 状态 Badge、空状态、导出按钮、标签渲染
端到端测试 Playwright 首页创建任务、查看报告、导出按钮、手动刷新
Docker 验收 docker compose + curl 启动、/health、页面可访问

2.3 测试目录建议

tests/
  conftest.py
  fixtures/
    xhs_hot_list.json
    xhs_search_notes.json
    xhs_comments_page_1.json
    xhs_comments_page_2_empty.json
    douyin_hot_list.json
    douyin_search_videos.json
    douyin_comments_page_1.json
    ai_comments_success.json
    ai_comments_invalid_json.txt
  unit/
    test_config.py
    test_models.py
    test_xiaohongshu_mapping.py
    test_douyin_mapping.py
    test_comment_pagination.py
    test_ai_schema.py
    test_report_stats.py
    test_export.py
    test_template_filters.py
  integration/
    test_task_creation.py
    test_task_flow_xiaohongshu.py
    test_task_flow_douyin.py
    test_failure_tolerance.py
    test_routes.py
  e2e/
    test_mvp_flow.py

3. 测试环境与命令

3.1 推荐依赖

pytest
pytest-cov
pytest-mock
respx
httpx
beautifulsoup4
playwright

说明:

  • respx 用于 mock httpx.Client 外部 HTTP 调用。
  • beautifulsoup4 用于断言 HTML 片段内容。
  • Playwright 仅覆盖关键页面流程,不替代单元测试。

工具使用原则

  • respx:用于 mock httpx.Client 发出的外部 HTTP 请求(TikHub API、AI API)。
  • pytest-mock:用于 mock Python 函数、类方法、time.sleep 调用和时间函数。
  • 两者不混用于同一外部请求的 mock。即:若某个测试需要拦截一个 HTTP 请求,必须使用 respx,不得用 pytest-mock.patch("httpx.Client.get") 替代。

3.2 测试数据库隔离与环境变量

测试数据库隔离策略

所有单元测试和集成测试统一使用内存数据库 sqlite:///:memory:,不依赖物理 test.db 文件,避免测试用例间状态污染(Flaky Tests)。

tests/conftest.py 中定义函数级别(scope="function")的数据库 fixture

@pytest.fixture(scope="function")
def db_session():
    engine = create_engine(
        "sqlite:///:memory:",
        connect_args={"check_same_thread": False},
    )
    Base.metadata.create_all(engine)
    session = Session(engine)
    yield session
    session.close()
    Base.metadata.drop_all(engine)

每个测试用例运行前自动创建全部表结构,运行后自动销毁,确保测试在绝对隔离的环境中执行。

禁止在集成测试中复用同一 engine 实例跨用例写入数据而不做 teardown。

环境变量

APP_ENV=test
DATABASE_URL=sqlite:///:memory:
TIKHUB_API_KEY=test-token
TIKHUB_BASE_URL=https://api.tikhub.test
AI_PROVIDER=openai-compatible
AI_BASE_URL=https://ai.test
AI_API_KEY=test-ai-key
AI_MODEL=test-model
AI_BATCH_SIZE=20
AI_CONCURRENCY=2
AI_MAX_RETRIES=3
AI_TIMEOUT_SECONDS=30

3.3 常用命令

pytest tests/unit -q
pytest tests/integration -q
pytest tests/unit tests/integration \
  --cov=app \
  --cov-branch \
  --cov-report=term-missing
pytest tests/e2e -q
docker compose up --build
curl -f http://localhost:8000/health

覆盖率豁免配置

pyproject.toml[tool.coverage.report] 节中添加以下配置,将模板层和静态资源排除在覆盖率统计之外:

[tool.coverage.report]
omit = [
    "app/templates/*",
    "app/static/*",
    "tests/*",
]

全局覆盖率目标维持 80%app/services/app/platforms/ 下的核心域覆盖率不低于 90%(通过 CI 脚本或 code review 人工把关,不在 pytest 命令层面强制差异化)。


4. Mock 数据规范

4.1 小红书 Mock 样例

xhs_hot_list.json 必须覆盖:

  • data.data.items[] 是真实热榜条目;
  • 外层 data.data.title 不应被误用为热点标题;
  • 热点字段包含 idtitlescore

xhs_search_notes.json 必须覆盖:

  • 至少 2 条 comments_count > 0 的笔记;
  • 至少 1 条 comments_count = 0 的笔记;
  • 字段包含 note.idnote.titlenote.descnote.comments_count

xhs_comments_page_1.json 必须覆盖:

  • 评论 ID 同时存在 comment_idid 时,优先 comment_id
  • 评论正文可来自 contenttext
  • 包含 like_countcreate_time

4.2 抖音 Mock 样例

douyin_hot_list.json 必须覆盖:

  • 热点字段包含 query_idtitlerankhot_score

douyin_search_videos.json 必须覆盖:

  • 视频字段包含 aweme_info.aweme_idaweme_info.descaweme_info.authoraweme_info.statistics

douyin_comments_page_1.json 必须覆盖:

  • 评论 ID 同时存在 comment_idcid 时,优先 comment_id
  • 评论正文来自 text
  • 包含 digg_countcreate_time

4.3 AI Mock 样例

成功响应:

[
  {
    "comment_id": "c1",
    "sentiment": "positive",
    "labels": ["价格实惠", "质量好"],
    "reason": "评论表达了明确认可"
  }
]

失败响应至少覆盖:

  • 非 JSON 文本;
  • JSON Object 而不是 JSON Array
  • 缺失 comment_id
  • comment_id 与输入不匹配;
  • sentiment 不在枚举值内;
  • labels 超过 3 个;
  • 混入 Markdown 或自然语言前缀。

4.4 HTTP 429 Mock Fixture

tests/conftest.py 中提供可复用的 429 响应 fixture

@pytest.fixture
def mock_429_response():
    return httpx.Response(
        status_code=429,
        headers={"Retry-After": "1"},
        json={"message": "Too Many Requests"},
    )

§8.2(分页限流重试)和 §13.3(任务级限流容错)均使用此 fixture,不各自重复定义。

4.5 评论字段缺失 Mock 数据

tests/fixtures/ 中新增以下两个文件:

  • xhs_comments_missing_fields.json:小红书评论列表,其中 like_countcreate_time 字段均不存在(非 null,而是 key 完全缺失)。
  • douyin_comments_missing_fields.json:抖音评论列表,其中 digg_countcreate_time 字段均不存在。

这两个 fixture 用于 §7.2 和 §7.5 的字段缺失测试用例。

4.6 AI 全情绪值 Mock 数据

tests/fixtures/ai_comments_success.json 中确保包含四种 sentiment 值各至少一条评论,用于统计逻辑(analysis_success_rate、情绪分布)的测试:

[
  {"comment_id": "c001", "sentiment": "positive", "labels": ["品质好"], "reason": ""},
  {"comment_id": "c002", "sentiment": "negative", "labels": ["物流慢"], "reason": ""},
  {"comment_id": "c003", "sentiment": "neutral", "labels": [], "reason": ""},
  {"comment_id": "c004", "sentiment": "unknown", "labels": [], "reason": ""}
]

ai_comments_success.json 已有其他用途,新增 ai_comments_all_sentiments.json 作为统计测试专用 fixture。


5. 配置与数据模型测试

5.1 配置校验

测试文件:tests/unit/test_config.py

用例:

  1. 默认配置读取成功。
  2. hot_limit 范围为 110。
  3. item_limit_per_hot 范围为 110。
  4. comment_limit_per_item 范围为 10100。
  5. AI_CONCURRENCY 默认值为 2。
  6. AI_MAX_RETRIES 默认值为 3。

5.2 SQLite 初始化

测试文件:tests/unit/test_models.py

用例:

  1. 应用启动时调用 Base.metadata.create_all(engine) 可创建所有表。
  2. SQLite engine 配置包含 check_same_thread=Falsetimeout=10
  3. 连接建立后启用 PRAGMA journal_mode=WAL

5.3 任务状态模型

测试文件:tests/unit/test_models.py

用例:

  1. Task.status 仅允许 runningsuccessfailed
  2. 不允许写入 partial_successpartial_failed
  3. analysis_status 允许 normalinsufficient
  4. 任务 status=success 时,analysis_success_rate < 0.8 只更新 analysis_status='insufficient',不改变 status='success'
  5. 任务 status=failed 时,analysis_success_rate 字段写入实际计算值,status 保持 'failed' 不变,不因 AI 成功率判断被覆盖为其他值。

6. 任务创建与恢复测试

6.1 创建任务 API

测试文件:tests/integration/test_task_creation.py

用例:

  1. POST /api/tasks 使用合法参数创建任务,返回 task_idstatus=running
  2. 创建任务保存平台和配置字段:
    • hot_limit
    • item_limit_per_hot
    • comment_limit_per_item
  3. platform 非法时返回 422。
  4. 配置值越界时返回 422。
  5. 同一平台可重复创建任务,任务彼此独立。

6.2 ThreadPoolExecutor 单任务约束

测试文件:tests/unit/test_task_executor.py

用例:

  1. 任务执行器初始化为 ThreadPoolExecutor(max_workers=1)
  2. 同时提交两个任务时,第二个任务必须等待第一个任务完成后才开始。

httpx 使用方式断言

测试文件:tests/unit/test_task_executor.py

用例:

  1. 后台任务执行函数不是协程:使用 inspect.iscoroutinefunction(run_task) 断言返回 False,确保其在 ThreadPoolExecutor 中以同步方式运行。
  2. 小红书平台客户端实例为 httpx.Client 同步类型:assert isinstance(client._http_client, httpx.Client)
  3. 抖音平台客户端实例为 httpx.Client 同步类型,同上。
  4. AI 服务客户端发出的请求使用 httpx.Client,不存在 httpx.AsyncClient 的实例化调用。

6.3 僵尸任务恢复

测试文件:tests/integration/test_task_recovery.py

用例:

  1. 数据库存在 status=running 的任务。
  2. 应用 lifespan 启动恢复逻辑执行后,该任务变为 status=failed
  3. error_stage=system
  4. error_type=unexpected_restart
  5. error_message=系统重启,任务被中断

7. 平台抓取测试

7.1 小红书字段映射

测试文件:tests/unit/test_xiaohongshu_mapping.py

用例:

  1. 热点标题从 data.data.items[].title 读取。
  2. 不使用外层 data.data.title 作为热点标题。
  3. 热点 id 映射到 source_hot_id
  4. score 映射到 heat_value
  5. 笔记 note.id 映射到 source_item_id
  6. 优先选择 comments_count > 0 的笔记。
  7. 有评论笔记不足目标数时,补充 comments_count = 0 的笔记。
  8. 平台返回笔记总数不足目标数时,不标记任务失败。

笔记搜索分页限制

测试文件:tests/unit/test_xiaohongshu_mapping.py

用例:

  1. 搜索笔记接口只请求第一页(page=1),不发起后续翻页请求。
  2. 首页返回 comments_count > 0 的笔记已满足目标数量时,不发起第二次搜索请求。
  3. 首页返回结果不足目标数量时,执行降级策略(补充 comments_count = 0 的笔记),而非翻页搜索。

7.2 小红书评论字段映射

测试文件:tests/unit/test_xiaohongshu_mapping.py

用例:

  1. source_comment_id 优先取 data.get("comment_id")
  2. comment_id 不存在时回退到 data.get("id")
  3. content 不存在时回退到 text
  4. like_countcreate_time 正确映射。
  5. 原始评论 JSON 保存到 raw_data
  6. like_count 字段缺失时,入库值为 null,不抛出异常,不阻断评论抓取流程。
  7. comment_timecreate_time / create_time_str)字段缺失时,入库值为 null,不抛出异常,不阻断评论抓取流程。

7.4 抖音字段映射

测试文件:tests/unit/test_douyin_mapping.py

用例:

  1. query_id 映射到 source_hot_id
  2. title 映射到热点标题。
  3. rank 映射到热点排名。
  4. hot_score 映射到 heat_value
  5. aweme_info.aweme_id 映射到 source_item_id
  6. aweme_info.desc 映射到标题或摘要。

7.5 抖音评论字段映射

测试文件:tests/unit/test_douyin_mapping.py

用例:

  1. source_comment_id 优先取 data.get("comment_id")
  2. comment_id 不存在时回退到 data.get("cid")
  3. text 映射到 content
  4. digg_count 映射到 like_count
  5. create_time 映射到 comment_time
  6. digg_count 字段缺失时,入库值为 null,不抛出异常,不阻断评论抓取流程。
  7. comment_timecreate_time / create_time_str)字段缺失时,入库值为 null,不抛出异常,不阻断评论抓取流程。

8. 评论分页、限流与去重测试

8.1 评论分页终止条件

测试文件:tests/unit/test_comment_pagination.py

用例:

  1. 抓取评论数达到 comment_limit_per_item 后停止。
  2. API 返回空评论列表后停止。
  3. 达到最大翻页轮次 5 后停止。
  4. API 未返回下一页游标时停止。

8.2 分页请求间隔

测试文件:tests/unit/test_comment_pagination.py

用例:

  1. 每次评论分页请求之间调用 time.sleep,间隔值在 1.0 到 2.0 秒范围内(含边界值)。断言方式:assert 1.0 <= mock_sleep.call_args[0][0] <= 2.0
  2. 收到 HTTP 429 后使用指数退避 1s → 2s → 4s。
  3. 429 退避完成后恢复基础分页间隔。

备注:若后续将间隔提取为配置项 CRAWL_PAGE_INTERVAL_SECONDS,改为读取配置值后断言,不硬编码数值。

8.3 评论去重

测试文件:tests/unit/test_comment_pagination.py

用例:

  1. 同一任务、同一内容条目、同一评论 ID 不重复入库。
  2. 重复抓取时更新已有记录或跳过重复记录。
  3. 不同任务下相同评论 ID 可分别保存。
  4. 同一内容出现在不同热点下时,按 task_id + hotspot_id + source_item_id 保留重复内容条目。

9. AI 分析测试

9.1 Prompt 输入构造

测试文件:tests/unit/test_ai_schema.py

用例:

  1. 输入给 LLM 的数据为 JSON Array。
  2. 每条输入包含 comment_idcontent
  3. 单条评论内容超过 150 字符时截断为前 150 字符。
  4. Prompt 明确要求原样回填 comment_id

9.2 AI 输出 Schema 校验

测试文件:tests/unit/test_ai_schema.py

用例:

  1. 严格 JSON Array 响应校验通过。
  2. 非 JSON 文本校验失败。
  3. JSON Object 校验失败。
  4. 缺失 comment_id 校验失败。
  5. comment_id 与输入不匹配时该条评论失败。
  6. sentiment 不在枚举值内校验失败。
  7. labels 超过 3 个时校验失败。
  8. 空标签数组允许通过,sentiment 可为 unknown

9.3 AI 重试与降级拆分

测试文件:tests/unit/test_ai_schema.py

用例:

  1. 单批 AI 请求失败后最多重试 AI_MAX_RETRIES=3 次。
  2. 同一批次连续 3 次整批解析失败(含重试)后,该批次所有评论的 ai_analysis_status 标记为 'failed',任务继续处理下一批次,不抛出异常。
  3. 当前批次失败不阻塞下一批次。

9.4 AI 并发粒度

测试文件:tests/unit/test_ai_schema.py

用例:

  1. 同一任务内最多 2 个 AI 批量请求同时运行。
  2. 并发粒度为跨内容条目。
  3. 同一内容条目的多批评论串行处理。

9.5 AI 成功率与任务质量状态

测试文件:tests/unit/test_ai_schema.py

用例:

  1. 成功评论数 / 总评论数 >= 80% 时,analysis_status=normal
  2. 成功评论数 / 总评论数 < 80% 时,analysis_status=insufficient
  3. analysis_status=insufficient 不改变任务 status
  4. 没有任何内容条目成功时,任务仍为 failed

10. 报告生成测试

10.1 情绪与标签统计

测试文件:tests/unit/test_report_stats.py

用例:

  1. 正向、负向、中性、未知评论数量正确。
  2. 情绪占比保留合理精度。
  3. 标签按字面值聚合。
  4. Top 5 标签按数量降序。
  5. 空标签不参与标签统计。

10.2 典型评论选取

测试文件:tests/unit/test_report_stats.py

用例:

  1. 每种情绪选取 12 条典型评论。
  2. 有点赞数字段时按点赞数降序。
  3. 点赞数缺失时按抓取顺序。
  4. 典型评论文本传给总结 AI 前截断为 150 字符。

10.3 内容条目级报告

测试文件:tests/unit/test_report_stats.py

用例:

  1. 内容条目级报告包含样本评论数量。
  2. 报告统计与评论结构化结果一致。
  3. report_type=itemhotspot_idcontent_item_id 均不为空。
  4. 生成 markdown_content 并保存。
  5. 总结 AI 失败时,summary 使用默认文本:总结生成失败,请查看详细数据
  6. report_type='item' 时,创建的报告记录 hotspot_id 不为空,content_item_id 不为空;任一字段为空时 report_service 抛出明确异常。
  7. report_type='hotspot' 时,创建的报告记录 hotspot_id 不为空,content_item_id 为空;若 content_item_id 非空则抛出明确异常。

10.4 热点级报告

测试文件:tests/unit/test_report_stats.py

用例:

  1. 热点级报告聚合热点下所有内容条目评论。
  2. 内容条目数量、样本数量、情绪数量与评论明细一致。
  3. report_type=hotspothotspot_id 不为空,content_item_id 为空。
  4. 生成 markdown_content 并保存。
  5. 总结 AI 失败不阻断报告创建。
  6. report_type='item' 时,创建的报告记录 hotspot_id 不为空,content_item_id 不为空;任一字段为空时 report_service 抛出明确异常。
  7. report_type='hotspot' 时,创建的报告记录 hotspot_id 不为空,content_item_id 为空;若 content_item_id 非空则抛出明确异常。

10.5 报告总结 AI 请求

测试文件:tests/unit/test_report_stats.py

用例:

  1. 内容条目级总结 AI 请求的输入包含统计数据摘要(正向 / 中性 / 负向各占比)和典型评论文本(每类至少 1 条)。
  2. 热点级总结 AI 请求的输入包含聚合情绪分布统计、Top 5 标签及出现次数,以及典型评论文本。
  3. 传入总结 AI 的单条评论文本截断为 150 字符,超出部分丢弃,不引发错误。
  4. 总结 AI 请求不使用 JSON Schema 约束(response_format 不为 json_objectjson_schema),输出期望为纯文本字符串。
  5. 总结 AI 请求超时时,报告中 summary 字段写入默认文本 总结生成失败,请查看上方统计数据,报告记录正常创建,不抛出异常,不阻断后续报告生成流程。
  6. 总结 AI 返回内容超过字数限制时执行截断:内容条目级总结超过 200 字时截断,热点级超过 300 字时截断。

11. 导出测试

11.1 CSV 导出

测试文件:tests/unit/test_export.py

用例:

  1. CSV 使用 UTF-8-SIG 编码。
  2. CSV 包含平台、任务 ID、热点、内容条目、评论 ID、评论内容、情绪、标签、点赞数、评论时间。
  3. labels 入库为 JSON Array 字符串,导出时转换为中文逗号拼接。
  4. 评论内容以 =+-@ 开头时添加单引号,防 CSV 公式注入。
  5. 导出内容与页面展示数据源一致。

11.2 文件名安全处理

测试文件:tests/unit/test_export.py

用例:

  1. 文件名格式为 {platform}_{task_id}_{hotspot_keyword}.csv
  2. hotspot_keyword 超过 20 字符时截断。
  3. /\:*?"<>| 替换为 _
  4. 连续多个 _ 合并为单个 _

11.3 Markdown 导出

测试文件:tests/unit/test_export.py

用例:

  1. 热点级 Markdown 读取 reports.markdown_content
  2. 内容条目级 Markdown 读取 reports.markdown_content
  3. 导出 Markdown 与页面报告使用同一份数据。
  4. 报告不存在时返回 404 或禁用按钮对应状态。

11.4 导出路由

测试文件:tests/integration/test_routes.py

用例:

  1. GET /api/export/items/{item_id}/comments.csv 返回 CSV 文件流。
  2. GET /api/export/hotspots/{hotspot_id}/comments.csv 返回热点下全部评论 CSV。
  3. GET /api/export/hotspots/{hotspot_id}.md 返回热点 Markdown。
  4. GET /api/export/items/{item_id}.md 返回内容条目 Markdown。

12. UI 与模板测试

12.1 状态展示

测试文件:tests/unit/test_template_filters.py

用例:

  1. running 渲染为运行中 Badge。
  2. success 渲染为已完成 Badge。
  3. failed 渲染为失败 Badge。
  4. 任务状态不测试 partial_successpartial_failed
  5. analysis_status=insufficient 渲染 AI 分析不足提示。

12.2 表单与校验

测试文件:tests/integration/test_routes.py

用例:

  1. 首页包含平台选择控件。
  2. 首页包含 hot_limititem_limit_per_hotcomment_limit_per_item 输入。
  3. 首页展示默认预估规模 1250。
  4. 422 错误可渲染到表单错误区域。

12.3 标签渲染

测试文件:tests/unit/test_template_filters.py

用例:

  1. from_json filter 可解析 labels JSON Array 字符串。
  2. 非法 JSON 返回空数组,不导致模板报错。
  3. 标签逐个渲染为 Badge。
  4. 空标签显示 -

12.4 空状态

测试文件:tests/integration/test_routes.py

用例:

  1. 无任务时首页展示空状态。
  2. 任务运行中且热点为空时展示抓取中 Spinner。
  3. 评论为空时内容详情页展示暂无评论数据。
  4. 报告尚未生成时展示报告生成中。

12.5 导出按钮状态

测试文件:tests/integration/test_routes.py

用例:

  1. task.status=running 时导出按钮 disabled。
  2. task.status=failed 且无成功内容条目时导出按钮 disabled。
  3. item.status=failed 时内容条目 CSV 导出按钮 disabled。
  4. 有报告和评论数据时导出按钮可点击。

13. 服务集成测试

13.1 小红书端到端服务流

测试文件:tests/integration/test_task_flow_xiaohongshu.py

流程:

  1. Mock 小红书热榜接口。
  2. Mock 小红书搜索笔记接口。
  3. Mock 小红书评论分页接口。
  4. Mock AI 评论分析接口。
  5. 创建小红书任务。
  6. 执行任务服务。
  7. 断言任务 status=success
  8. 断言热点、内容条目、评论、报告均入库。
  9. 断言 raw_data 已保存。

13.2 抖音端到端服务流

测试文件:tests/integration/test_task_flow_douyin.py

流程:

  1. Mock 抖音热点接口。
  2. Mock 抖音视频搜索接口。
  3. Mock 抖音评论分页接口。
  4. Mock AI 评论分析接口。
  5. 创建抖音任务。
  6. 执行任务服务。
  7. 断言任务 status=success
  8. 断言热点、内容条目、评论、报告均入库。

13.3 单个内容条目失败但任务继续

测试文件:tests/integration/test_failure_tolerance.py

用例:

  1. 一个内容条目评论接口返回 429 后超过最大重试。
  2. 该内容条目标记 failed
  3. 后续内容条目继续处理。
  4. 至少一个内容条目成功时,任务最终 status=success
  5. 任务展示失败内容条目数和错误摘要。

13.4 全局失败

测试文件:tests/integration/test_failure_tolerance.py

用例:

  1. 热点接口失败时任务 status=failed
  2. 所有内容条目均失败时任务 status=failed
  3. 失败任务包含 error_stageerror_typeerror_message

13.5 跨热点重复内容条目场景

测试文件:tests/integration/test_failure_tolerance.py

用例:

  1. 同一 source_item_id 同时出现在两个不同热点的搜索结果中时,系统以不同 hotspot_id 分别创建两条 content_items 记录,断言数据库中存在 2 条该 source_item_id 的记录,各自归属于对应的 hotspot_id
  2. 同一任务内,同一热点下相同 source_item_id 重复出现时,数据库中该热点下该 source_item_id 只保留 1 条记录(去重),不抛出异常,不阻断任务。

14. Playwright E2E 测试(P2,时间允许时实现)

当前 MVP 不实现本节内容。

原因:在 4 天单人开发周期内,Playwright 环境配置及 AI 长耗时任务的异步等待调优成本较高,性价比低于手工验收。

Day 4 端到端验收完全依赖 §15.2 手工验收清单,后者覆盖了所有 P0 用户流程。

后续版本实现 Playwright 时,须补充以下前提条件:

  1. e2e 测试启动独立测试服务器实例(APP_ENV=test)。
  2. 外部 API 调用由 respx 在进程内拦截,不发起真实网络请求。
  3. 测试前后清理 SQLite 测试数据库(或使用内存数据库)。
  4. 明确运行环境:若使用本地 uvicorn 服务,在 Docker 验收前执行;若在 Docker 环境中运行,须先完成 §15 Docker 验收。

15. Docker 与手工验收测试

15.1 Docker 启动

测试命令:

docker compose up --build
curl -f http://localhost:8000/health

验收:

  • Docker Compose 可启动。
  • /health 返回 200。
  • 首页可访问。
  • SQLite 文件生成在 ./data/app.db

15.2 MVP 手工验收清单

  1. 创建小红书默认任务。
  2. 任务列表展示创建时间、状态、AI 分析状态、成功 X / 共 Y 条内容条目。
  3. 刷新任务状态。
  4. 查看热点列表。
  5. 查看热点级报告。
  6. 查看内容条目详情。
  7. 查看评论明细。
  8. 导出 CSV 评论明细。
  9. 导出热点 Markdown 报告。
  10. 导出内容条目 Markdown 报告。
  11. 创建抖音默认任务并重复 210。
  12. 输入非法配置,确认前端和后端均拒绝。

16. 覆盖率与发布门槛

16.1 最低覆盖要求

  • 单元测试覆盖率建议不低于 80%。
  • platforms/services/ai_service.pyservices/report_service.pyservices/export_service.py 必须有关键行为测试。
  • UI 模板不强制覆盖率数字,但关键状态和导出按钮必须有模板测试或 e2e 测试。

16.2 合并前必须通过

pytest tests/unit -q
pytest tests/integration -q
pytest tests/unit tests/integration \
  --cov=app \
  --cov-branch \
  --cov-report=term-missing \
  --cov-fail-under=80

覆盖率统计豁免 templates/static/ 目录,详见 pyproject.toml 配置。

branch coverage 对任务状态机和 AI 成功率阈值判断有特殊检测价值。

如修改部署相关文件:

docker compose up --build
curl -f http://localhost:8000/health

17. 开发顺序建议

Day 1 测试优先级

  1. 配置校验测试。
  2. 数据模型测试。
  3. 任务创建 API 测试。
  4. 僵尸任务恢复测试。
  5. 首页模板基础渲染测试。

Day 2 测试优先级

  1. 小红书字段映射测试。
  2. 小红书评论分页测试。
  3. 小红书降级选择测试。
  4. 抖音字段映射测试。
  5. 429 退避与单条失败继续测试。

Day 3 测试优先级

  1. AI 输入构造测试。
  2. AI 输出 schema 测试。
  3. AI 重试测试。
  4. analysis_success_rate 测试。
  5. 报告统计与 Markdown 生成测试。

(依赖 Day 3 第 1-4 条 AI 分析测试完成后方可验证完整集成路径)

Day 4 测试优先级

  1. CSV / Markdown 导出测试。
  2. 模板状态和空状态测试。
  3. Playwright E2E 测试(已降级为 P2,见 §14)——Day 4 端到端验收直接执行 §15.2 手工验收清单。
  4. Docker 启动验收。

18. 变更日志

日期 版本 变更内容
2026-07-01 v1.0 基于 PRD、FeatureSummary、DevelopmentPlan、UIDesign 生成 TDD 测试驱动开发计划,覆盖配置、数据模型、任务、平台抓取、评论分页、AI 分析、报告、导出、UI 模板、集成流程、Playwright 与 Docker 验收。