Files
hot_comment_radar/docs/TDD.md
T

915 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TDD.md:小红书 / 抖音热榜评论抓取 + AI 分析报告工具
## 1. 文档信息
- 文档阶段:TDDTest-Driven Development,测试驱动开发计划)
- 需求依据:`docs/PRD.md``docs/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 测试目录建议
```text
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 推荐依赖
```text
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
```python
@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。
#### 环境变量
```text
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 常用命令
```bash
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]` 节中添加以下配置,将模板层和静态资源排除在覆盖率统计之外:
```toml
[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` 不应被误用为热点标题;
- 热点字段包含 `id``title``score`
`xhs_search_notes.json` 必须覆盖:
- 至少 2 条 `comments_count > 0` 的笔记;
- 至少 1 条 `comments_count = 0` 的笔记;
- 字段包含 `note.id``note.title``note.desc``note.comments_count`
`xhs_comments_page_1.json` 必须覆盖:
- 评论 ID 同时存在 `comment_id``id` 时,优先 `comment_id`
- 评论正文可来自 `content``text`
- 包含 `like_count``create_time`
### 4.2 抖音 Mock 样例
`douyin_hot_list.json` 必须覆盖:
- 热点字段包含 `query_id``title``rank``hot_score`
`douyin_search_videos.json` 必须覆盖:
- 视频字段包含 `aweme_info.aweme_id``aweme_info.desc``aweme_info.author``aweme_info.statistics`
`douyin_comments_page_1.json` 必须覆盖:
- 评论 ID 同时存在 `comment_id``cid` 时,优先 `comment_id`
- 评论正文来自 `text`
- 包含 `digg_count``create_time`
### 4.3 AI Mock 样例
成功响应:
```json
[
{
"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
```python
@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_count``create_time` 字段均不存在(非 null,而是 key 完全缺失)。
- `douyin_comments_missing_fields.json`:抖音评论列表,其中 `digg_count``create_time` 字段均不存在。
这两个 fixture 用于 §7.2 和 §7.5 的字段缺失测试用例。
### 4.6 AI 全情绪值 Mock 数据
`tests/fixtures/ai_comments_success.json` 中确保包含四种 sentiment 值各至少一条评论,用于统计逻辑(`analysis_success_rate`、情绪分布)的测试:
```json
[
{"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=False``timeout=10`
3. 连接建立后启用 `PRAGMA journal_mode=WAL`
### 5.3 任务状态模型
测试文件:`tests/unit/test_models.py`
用例:
1. `Task.status` 仅允许 `running``success``failed`
2. 不允许写入 `partial_success``partial_failed`
3. `analysis_status` 允许 `normal``insufficient`
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_id``status=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_count``create_time` 正确映射。
5. 原始评论 JSON 保存到 `raw_data`
6. `like_count` 字段缺失时,入库值为 `null`,不抛出异常,不阻断评论抓取流程。
7. `comment_time``create_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_time``create_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_id``content`
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=item``hotspot_id``content_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=hotspot``hotspot_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_object``json_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_success``partial_failed`
5. `analysis_status=insufficient` 渲染 AI 分析不足提示。
### 12.2 表单与校验
测试文件:`tests/integration/test_routes.py`
用例:
1. 首页包含平台选择控件。
2. 首页包含 `hot_limit``item_limit_per_hot``comment_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_stage``error_type``error_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 启动
测试命令:
```bash
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.py``services/report_service.py``services/export_service.py` 必须有关键行为测试。
- UI 模板不强制覆盖率数字,但关键状态和导出按钮必须有模板测试或 e2e 测试。
### 16.2 合并前必须通过
```bash
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 成功率阈值判断有特殊检测价值。
如修改部署相关文件:
```bash
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 验收。 |