diff --git a/featuredoc/tdd_plan.md b/featuredoc/tdd_plan.md
new file mode 100644
index 0000000..042f976
--- /dev/null
+++ b/featuredoc/tdd_plan.md
@@ -0,0 +1,2179 @@
+# TDD 实施评估与计划
+
+| 文档类型 | **Testing Strategy (测试驱动开发实施计划)** |
+| --- | --- |
+| **项目名称** | SmartAudit (AI 营销内容合规审核平台) |
+| **版本号** | V1.0 |
+| **发布日期** | 2026-02-02 |
+| **关联文档** | tasks.md, DevelopmentPlan.md, FeatureSummary.md |
+
+---
+
+## 版本历史 (Version History)
+
+| 版本 | 日期 | 作者 | 变更说明 |
+| --- | --- | --- | --- |
+| V1.0 | 2026-02-02 | Claude | 初稿:项目诊断、TDD可行性评估、实施计划 |
+
+---
+
+## 目录
+
+1. [项目现状诊断](#1-项目现状诊断)
+2. [TDD 可行性评估](#2-tdd-可行性评估)
+3. [测试金字塔架构](#3-测试金字塔架构)
+4. [后端测试策略](#4-后端测试策略)
+5. [前端测试策略](#5-前端测试策略)
+6. [AI 模型测试策略](#6-ai-模型测试策略)
+7. [端到端测试策略](#7-端到端测试策略)
+8. [实施路线图](#8-实施路线图)
+9. [测试覆盖率目标](#9-测试覆盖率目标)
+10. [工具链配置](#10-工具链配置)
+11. [团队规范与培训](#11-团队规范与培训)
+12. [风险与挑战](#12-风险与挑战)
+
+---
+
+## 1. 项目现状诊断
+
+### 1.1 代码库状态
+
+| 维度 | 当前状态 | 评估 |
+| --- | --- | --- |
+| **源代码** | 零代码,纯需求阶段 | ✅ 最佳TDD切入点 |
+| **文档完整度** | 5,796行,覆盖PRD/RD/技术架构/UI | ✅ 需求明确 |
+| **技术选型** | 已确定:FastAPI + Next.js | ✅ 测试生态成熟 |
+| **任务拆解** | 77个开发任务,优先级明确 | ✅ 粒度适合TDD |
+| **验收标准** | 每个功能有量化指标 | ✅ 可直接转化为测试用例 |
+| **CI/CD** | 已规划,待实施 | ⚠️ 需同步搭建 |
+
+### 1.2 技术栈测试生态评估
+
+| 技术 | 测试框架支持 | 生态成熟度 | Mock/Stub 支持 |
+| --- | --- | --- | --- |
+| **FastAPI** | pytest + httpx | ⭐⭐⭐⭐⭐ | TestClient 内置 |
+| **Celery** | pytest-celery | ⭐⭐⭐⭐ | eager mode 支持 |
+| **PostgreSQL** | TestContainers | ⭐⭐⭐⭐⭐ | 容器化隔离 |
+| **Next.js/React** | Vitest + RTL | ⭐⭐⭐⭐⭐ | MSW 拦截 |
+| **Zustand** | 原生测试支持 | ⭐⭐⭐⭐ | 无需特殊处理 |
+| **Socket.io** | jest-socket.io-mock | ⭐⭐⭐ | 需手动 Mock |
+
+### 1.3 项目复杂度分析
+
+```
+复杂度热力图:
+
+┌─────────────────────────────────────────────────────────┐
+│ 模块 │ 业务复杂度 │ 测试难度 │
+├─────────────────────────────────────────────────────────┤
+│ 认证与权限 (RBAC) │ ██░░░ │ ██░░░ │
+│ Brief 解析 (LLM) │ ████░ │ ████░ │
+│ 规则引擎 │ ███░░ │ ██░░░ │
+│ 视频上传 (Tus) │ ██░░░ │ ███░░ │
+│ ASR/OCR/CV 流水线 │ █████ │ █████ │ ← 最高
+│ 多模态时间戳对齐 │ █████ │ █████ │ ← 最高
+│ WebSocket 进度推送 │ ██░░░ │ ███░░ │
+│ 审核决策流程 │ ███░░ │ ██░░░ │
+│ 数据看板 │ ██░░░ │ ██░░░ │
+│ 移动端 H5 │ ██░░░ │ ███░░ │
+└─────────────────────────────────────────────────────────┘
+```
+
+**诊断结论**:
+- ✅ **绿灯项目**:零代码起步,是实施TDD的理想时机
+- ✅ 技术栈测试生态成熟,无明显阻碍
+- ⚠️ AI流水线(ASR/OCR/CV)测试需特殊策略
+
+---
+
+## 2. TDD 可行性评估
+
+### 2.1 综合评分
+
+| 评估维度 | 评分 | 说明 |
+| --- | --- | --- |
+| **需求明确性** | ⭐⭐⭐⭐⭐ | PRD/RD 详尽,用户故事完整 |
+| **功能粒度** | ⭐⭐⭐⭐⭐ | 77个任务,边界清晰 |
+| **技术可测性** | ⭐⭐⭐⭐ | 主流框架,生态成熟 |
+| **团队规模** | ⭐⭐⭐⭐ | 8人精干团队,沟通高效 |
+| **时间充裕度** | ⭐⭐⭐⭐ | 11周排期,非极限压缩 |
+| **验收标准量化** | ⭐⭐⭐⭐⭐ | 每个功能有明确KPI |
+
+**总体评估:🟢 高度可行 (95分/100)**
+
+### 2.2 TDD 适用性分析
+
+| 模块类型 | TDD 适用度 | 推荐策略 |
+| --- | --- | --- |
+| **纯业务逻辑** | ⭐⭐⭐⭐⭐ | 严格 TDD(先写测试) |
+| **API 接口** | ⭐⭐⭐⭐⭐ | 契约测试 + TDD |
+| **数据模型** | ⭐⭐⭐⭐ | TDD + Schema 验证 |
+| **规则引擎** | ⭐⭐⭐⭐⭐ | 表格驱动测试 + TDD |
+| **AI 模型调用** | ⭐⭐⭐ | 混合模式(输入输出验证) |
+| **AI Prompt** | ⭐⭐ | 标注测试集验证 |
+| **UI 组件** | ⭐⭐⭐⭐ | 组件级 TDD |
+| **E2E 流程** | ⭐⭐ | BDD + E2E 测试 |
+
+### 2.3 TDD 实施模式选择
+
+推荐采用 **"分层混合 TDD"** 模式:
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ 分层混合 TDD 模式 │
+├──────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 第1层:严格 TDD (100% 覆盖) │ │
+│ │ • 工具函数 (utils) │ │
+│ │ • 数据验证器 (validators) │ │
+│ │ • 规则引擎 (rule engine) │ │
+│ │ • 业务逻辑服务 (services) │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 第2层:契约优先 (Contract-First) │ │
+│ │ • API 接口 → 先定义 OpenAPI │ │
+│ │ • 数据模型 → 先定义 Schema │ │
+│ │ • WebSocket 消息 → 先定义消息格式 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 第3层:标注集验证 (AI 模型) │ │
+│ │ • ASR/OCR/CV → 标注测试集 + 阈值验证 │ │
+│ │ • LLM Prompt → Few-shot 示例 + 定期回归 │ │
+│ │ • 向量检索 → 召回率/精确率评估 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 第4层:行为驱动 (BDD + E2E) │ │
+│ │ • 用户故事 → Playwright E2E │ │
+│ │ • 关键路径 → 冒烟测试 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+└──────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 3. 测试金字塔架构
+
+### 3.1 测试层级分布
+
+```
+ ┌─────────────┐
+ │ E2E 测试 │ 5%
+ │ (Playwright)│
+ └──────┬──────┘
+ │
+ ┌──────────┴──────────┐
+ │ 集成测试 │ 20%
+ │ (API + DB + 外部) │
+ └──────────┬──────────┘
+ │
+ ┌──────────────────┴──────────────────┐
+ │ 单元测试 │ 75%
+ │ (函数、类、组件、纯逻辑) │
+ └─────────────────────────────────────┘
+```
+
+### 3.2 各层级职责划分
+
+| 层级 | 占比 | 覆盖范围 | 执行频率 | 执行时间 |
+| --- | --- | --- | --- | --- |
+| **单元测试** | 75% | 函数/类/组件/纯逻辑 | 每次提交 | < 30秒 |
+| **集成测试** | 20% | API/DB/消息队列/外部服务 | 每次PR | < 5分钟 |
+| **E2E 测试** | 5% | 完整用户流程 | 每日/发布前 | < 15分钟 |
+
+### 3.3 SmartAudit 测试分层详情
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ SmartAudit 测试分层 │
+├────────────────────────────────────────────────────────────────────┤
+│ │
+│ 【E2E 测试层】 Playwright │
+│ ├─ 达人流程:上传视频 → 等待审核 → 查看结果 → 申诉 │
+│ ├─ 代理商流程:配置Brief → 审核视频 → 驳回/通过 │
+│ └─ 品牌方流程:查看看板 → 配置规则 → 审批强制通过 │
+│ │
+│ 【集成测试层】 pytest + TestContainers │
+│ ├─ API 接口测试 (httpx TestClient) │
+│ ├─ 数据库集成测试 (PostgreSQL + pgvector) │
+│ ├─ Redis 缓存测试 │
+│ ├─ Celery 任务测试 (eager mode) │
+│ ├─ 文件上传测试 (OSS Mock) │
+│ └─ WebSocket 推送测试 │
+│ │
+│ 【单元测试层】 │
+│ │ │
+│ │ 后端 (pytest) 前端 (Vitest) │
+│ │ ├─ 工具函数 ├─ 工具函数 │
+│ │ ├─ 数据验证器 ├─ 格式化函数 │
+│ │ ├─ 规则引擎逻辑 ├─ 状态管理 (Zustand) │
+│ │ ├─ 时间戳对齐算法 ├─ React Hooks │
+│ │ ├─ Brief 解析逻辑 ├─ UI 组件 │
+│ │ ├─ 业务服务方法 └─ 表单验证逻辑 │
+│ │ └─ Pydantic 模型 │
+│ │ │
+│ │ AI 模型 (标注测试集) │
+│ │ ├─ ASR 输出格式验证 │
+│ │ ├─ OCR 输出格式验证 │
+│ │ ├─ CV 检测结果验证 │
+│ │ ├─ LLM 输出解析验证 │
+│ │ └─ 向量相似度计算验证 │
+│ │ │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 4. 后端测试策略
+
+### 4.1 测试框架选型
+
+| 用途 | 工具 | 说明 |
+| --- | --- | --- |
+| **测试框架** | pytest | Python 标准,插件生态丰富 |
+| **异步测试** | pytest-asyncio | FastAPI 异步支持 |
+| **覆盖率** | pytest-cov | 覆盖率报告 |
+| **Mock** | unittest.mock / pytest-mock | 依赖模拟 |
+| **Fixture** | pytest fixtures | 测试数据管理 |
+| **参数化** | @pytest.mark.parametrize | 表格驱动测试 |
+| **容器化测试** | TestContainers | DB/Redis/MQ 隔离 |
+| **API 测试** | httpx + TestClient | FastAPI 内置 |
+| **Celery 测试** | celery.contrib.testing | 任务测试 |
+| **快照测试** | syrupy | JSON 输出验证 |
+
+### 4.2 目录结构
+
+```
+backend/
+├── tests/
+│ ├── __init__.py
+│ ├── conftest.py # 全局 fixtures
+│ │
+│ ├── unit/ # 单元测试 (75%)
+│ │ ├── __init__.py
+│ │ ├── test_validators.py # 数据验证器
+│ │ ├── test_utils.py # 工具函数
+│ │ ├── test_rule_engine.py # 规则引擎
+│ │ ├── test_timestamp.py # 时间戳对齐
+│ │ ├── test_brief_parser.py # Brief 解析逻辑
+│ │ └── services/
+│ │ ├── test_auth.py
+│ │ ├── test_brief.py
+│ │ ├── test_video.py
+│ │ └── test_report.py
+│ │
+│ ├── integration/ # 集成测试 (20%)
+│ │ ├── __init__.py
+│ │ ├── conftest.py # DB/Redis fixtures
+│ │ ├── test_api_auth.py # 认证 API
+│ │ ├── test_api_brief.py # Brief API
+│ │ ├── test_api_video.py # 视频 API
+│ │ ├── test_api_report.py # 报告 API
+│ │ ├── test_db_models.py # 数据库模型
+│ │ ├── test_celery_tasks.py # 异步任务
+│ │ └── test_websocket.py # WebSocket
+│ │
+│ ├── ai/ # AI 模型测试
+│ │ ├── __init__.py
+│ │ ├── conftest.py # 测试集加载
+│ │ ├── test_asr.py # ASR 输出验证
+│ │ ├── test_ocr.py # OCR 输出验证
+│ │ ├── test_cv.py # CV 检测验证
+│ │ ├── test_llm.py # LLM 输出解析
+│ │ └── test_embedding.py # 向量生成验证
+│ │
+│ ├── e2e/ # 端到端测试 (5%)
+│ │ ├── __init__.py
+│ │ └── test_workflows.py # 完整流程
+│ │
+│ └── fixtures/ # 测试数据
+│ ├── briefs/ # 测试 Brief 文件
+│ ├── videos/ # 测试视频文件
+│ ├── rules/ # 测试规则集
+│ └── snapshots/ # 快照数据
+│
+├── pytest.ini # pytest 配置
+└── pyproject.toml # 项目配置
+```
+
+### 4.3 核心测试用例设计
+
+#### 4.3.1 规则引擎测试 (表格驱动)
+
+```python
+# tests/unit/test_rule_engine.py
+
+import pytest
+from app.services.rule_engine import RuleEngine
+
+class TestProhibitedWordDetection:
+ """违禁词检测测试 - 表格驱动"""
+
+ @pytest.mark.parametrize("text,expected_violations,context", [
+ # 广告语境下的违禁词 - 应检出
+ ("这是全网销量第一的产品", ["全网第一"], "advertisement"),
+ ("我们是行业领导者", ["行业领导者"], "advertisement"),
+ ("史上最低价促销", ["史上最低价"], "advertisement"),
+
+ # 日常语境下的相同词 - 不应检出
+ ("今天是我最开心的一天", [], "daily_conversation"),
+ ("这是我第一次来这里", [], "daily_conversation"),
+
+ # 边界情况
+ ("", [], "advertisement"),
+ ("这是一个普通的产品介绍", [], "advertisement"),
+
+ # 组合违禁词
+ ("全网销量第一,史上最低价", ["全网第一", "史上最低价"], "advertisement"),
+ ])
+ def test_prohibited_word_detection(self, text, expected_violations, context):
+ """验证违禁词检测的准确性"""
+ engine = RuleEngine()
+ result = engine.detect_prohibited_words(text, context=context)
+
+ assert set(result.violations) == set(expected_violations)
+```
+
+#### 4.3.2 时间戳对齐算法测试
+
+```python
+# tests/unit/test_timestamp.py
+
+import pytest
+from app.utils.timestamp_align import TimestampAligner
+
+class TestMultiModalAlignment:
+ """多模态时间戳对齐测试"""
+
+ @pytest.fixture
+ def aligner(self):
+ return TimestampAligner(tolerance_ms=500)
+
+ @pytest.mark.parametrize("asr_ts,ocr_ts,cv_ts,expected_merged", [
+ # 完全对齐
+ (1000, 1000, 1000, 1000),
+ # 容差范围内对齐
+ (1000, 1200, 1100, 1100), # 取中位数
+ # 超出容差
+ (1000, 2000, 3000, None), # 不合并
+ ])
+ def test_timestamp_alignment(self, aligner, asr_ts, ocr_ts, cv_ts, expected_merged):
+ """验证时间戳对齐逻辑"""
+ events = [
+ {"source": "asr", "timestamp_ms": asr_ts, "content": "test"},
+ {"source": "ocr", "timestamp_ms": ocr_ts, "content": "test"},
+ {"source": "cv", "timestamp_ms": cv_ts, "content": "logo_detected"},
+ ]
+
+ merged = aligner.merge_events(events)
+
+ if expected_merged:
+ assert len(merged) == 1
+ assert merged[0]["timestamp_ms"] == expected_merged
+ else:
+ assert len(merged) == 3 # 未合并
+
+ def test_duration_calculation_accuracy(self, aligner):
+ """验证时长统计误差 ≤ 0.5秒"""
+ events = [
+ {"timestamp_ms": 0, "type": "product_appear"},
+ {"timestamp_ms": 5500, "type": "product_disappear"},
+ ]
+
+ duration = aligner.calculate_duration(events)
+
+ # 误差应 ≤ 500ms
+ assert abs(duration - 5500) <= 500
+```
+
+#### 4.3.3 Brief 解析测试
+
+```python
+# tests/unit/test_brief_parser.py
+
+import pytest
+from app.services.brief_parser import BriefParser
+
+class TestBriefParsing:
+ """Brief 解析逻辑测试"""
+
+ @pytest.fixture
+ def parser(self):
+ return BriefParser()
+
+ def test_extract_selling_points(self, parser):
+ """验证卖点提取"""
+ brief_content = """
+ 产品核心卖点:
+ 1. 24小时持妆
+ 2. 天然成分
+ 3. 敏感肌适用
+ """
+
+ result = parser.extract_selling_points(brief_content)
+
+ assert "24小时持妆" in result.selling_points
+ assert "天然成分" in result.selling_points
+ assert "敏感肌适用" in result.selling_points
+
+ def test_extract_prohibited_words(self, parser):
+ """验证禁忌词提取"""
+ brief_content = """
+ 禁止使用的词汇:
+ - 药用
+ - 治疗
+ - 根治
+ """
+
+ result = parser.extract_prohibited_words(brief_content)
+
+ assert set(result.prohibited_words) == {"药用", "治疗", "根治"}
+
+ def test_conflict_detection(self, parser):
+ """验证 Brief 与平台规则冲突检测"""
+ brief_rules = {"allowed_words": ["最佳效果"]}
+ platform_rules = {"prohibited_words": ["最佳"]}
+
+ conflicts = parser.detect_conflicts(brief_rules, platform_rules)
+
+ assert len(conflicts) == 1
+ assert "最佳效果" in conflicts[0]["conflicting_term"]
+```
+
+### 4.4 集成测试策略
+
+#### 4.4.1 数据库集成测试
+
+```python
+# tests/integration/conftest.py
+
+import pytest
+from testcontainers.postgres import PostgresContainer
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+@pytest.fixture(scope="session")
+def postgres_container():
+ """启动 PostgreSQL 测试容器"""
+ with PostgresContainer("postgres:15-alpine") as postgres:
+ yield postgres
+
+@pytest.fixture(scope="function")
+def db_session(postgres_container):
+ """每个测试函数独立的数据库会话"""
+ engine = create_engine(postgres_container.get_connection_url())
+ Session = sessionmaker(bind=engine)
+ session = Session()
+
+ # 创建表
+ Base.metadata.create_all(engine)
+
+ yield session
+
+ # 清理
+ session.rollback()
+ session.close()
+```
+
+#### 4.4.2 API 集成测试
+
+```python
+# tests/integration/test_api_brief.py
+
+import pytest
+from httpx import AsyncClient
+from app.main import app
+
+class TestBriefAPI:
+ """Brief API 集成测试"""
+
+ @pytest.fixture
+ async def client(self):
+ async with AsyncClient(app=app, base_url="http://test") as ac:
+ yield ac
+
+ @pytest.fixture
+ async def auth_headers(self, client):
+ """获取认证头"""
+ response = await client.post("/auth/login", json={
+ "username": "test_agency",
+ "password": "password"
+ })
+ token = response.json()["access_token"]
+ return {"Authorization": f"Bearer {token}"}
+
+ async def test_upload_brief_pdf(self, client, auth_headers, tmp_path):
+ """测试 Brief PDF 上传"""
+ # 准备测试文件
+ test_pdf = tmp_path / "test_brief.pdf"
+ test_pdf.write_bytes(b"%PDF-1.4 test content")
+
+ with open(test_pdf, "rb") as f:
+ response = await client.post(
+ "/api/v1/briefs/upload",
+ files={"file": ("test.pdf", f, "application/pdf")},
+ headers=auth_headers
+ )
+
+ assert response.status_code == 202
+ assert "task_id" in response.json()
+
+ async def test_get_brief_parsing_result(self, client, auth_headers):
+ """测试获取 Brief 解析结果"""
+ # 假设已有解析完成的 Brief
+ brief_id = "test-brief-id"
+
+ response = await client.get(
+ f"/api/v1/briefs/{brief_id}",
+ headers=auth_headers
+ )
+
+ assert response.status_code == 200
+ result = response.json()
+ assert "selling_points" in result
+ assert "prohibited_words" in result
+```
+
+### 4.5 Celery 异步任务测试
+
+```python
+# tests/integration/test_celery_tasks.py
+
+import pytest
+from unittest.mock import patch, MagicMock
+from app.tasks.video_auditing import audit_video_task
+
+class TestVideoAuditingTask:
+ """视频审核异步任务测试"""
+
+ @pytest.fixture
+ def mock_ai_services(self):
+ """Mock 所有 AI 服务"""
+ with patch("app.tasks.video_auditing.ASRService") as mock_asr, \
+ patch("app.tasks.video_auditing.OCRService") as mock_ocr, \
+ patch("app.tasks.video_auditing.CVService") as mock_cv:
+
+ mock_asr.return_value.transcribe.return_value = {
+ "text": "这是测试文本",
+ "timestamps": [{"start": 0, "end": 1000, "text": "这是测试文本"}]
+ }
+
+ mock_ocr.return_value.extract.return_value = {
+ "frames": [{"timestamp": 500, "text": "字幕内容"}]
+ }
+
+ mock_cv.return_value.detect.return_value = {
+ "logos": [],
+ "objects": [{"timestamp": 500, "object": "product"}]
+ }
+
+ yield {"asr": mock_asr, "ocr": mock_ocr, "cv": mock_cv}
+
+ def test_video_audit_task_success(self, mock_ai_services, db_session):
+ """测试视频审核任务成功执行"""
+ task_id = "test-task-id"
+ video_url = "https://test.oss.com/test.mp4"
+ brief_id = "test-brief-id"
+
+ # 使用 eager 模式同步执行
+ result = audit_video_task.apply(
+ args=[task_id, video_url, brief_id]
+ ).get()
+
+ assert result["status"] == "completed"
+ assert "report" in result
+ assert "risk_items" in result["report"]
+
+ def test_video_audit_task_with_violations(self, mock_ai_services, db_session):
+ """测试检测到违规时的处理"""
+ # 修改 Mock 返回值,模拟检测到违禁词
+ mock_ai_services["asr"].return_value.transcribe.return_value = {
+ "text": "这是全网销量第一的产品",
+ "timestamps": [{"start": 0, "end": 1000, "text": "这是全网销量第一的产品"}]
+ }
+
+ result = audit_video_task.apply(
+ args=["test-task", "url", "brief-id"]
+ ).get()
+
+ assert result["status"] == "completed"
+ assert len(result["report"]["risk_items"]) > 0
+ assert any(
+ item["type"] == "prohibited_word"
+ for item in result["report"]["risk_items"]
+ )
+```
+
+---
+
+## 5. 前端测试策略
+
+### 5.1 测试框架选型
+
+| 用途 | 工具 | 说明 |
+| --- | --- | --- |
+| **单元测试框架** | Vitest | Vite 原生,极速执行 |
+| **组件测试** | @testing-library/react | 用户行为驱动 |
+| **DOM 断言** | @testing-library/jest-dom | 扩展匹配器 |
+| **Mock 服务** | MSW (Mock Service Worker) | API 拦截 |
+| **E2E 测试** | Playwright | 跨浏览器 |
+| **视觉回归** | Percy / Chromatic | 截图对比 |
+| **覆盖率** | @vitest/coverage-v8 | 覆盖率报告 |
+
+### 5.2 目录结构
+
+```
+frontend/
+├── src/
+│ ├── components/
+│ │ ├── Button/
+│ │ │ ├── Button.tsx
+│ │ │ ├── Button.test.tsx # 组件测试
+│ │ │ └── Button.stories.tsx # Storybook (可选)
+│ │ └── ...
+│ │
+│ ├── hooks/
+│ │ ├── useAuth.ts
+│ │ ├── useAuth.test.ts # Hook 测试
+│ │ └── ...
+│ │
+│ ├── services/
+│ │ ├── api.ts
+│ │ ├── api.test.ts # 服务测试
+│ │ └── ...
+│ │
+│ ├── store/
+│ │ ├── auth.ts
+│ │ ├── auth.test.ts # 状态测试
+│ │ └── ...
+│ │
+│ └── lib/
+│ ├── utils.ts
+│ ├── utils.test.ts # 工具函数测试
+│ └── ...
+│
+├── tests/
+│ ├── setup.ts # 测试全局配置
+│ ├── mocks/
+│ │ ├── handlers.ts # MSW 处理器
+│ │ └── server.ts # MSW 服务器
+│ │
+│ ├── integration/ # 集成测试
+│ │ ├── BriefUpload.test.tsx
+│ │ ├── VideoUpload.test.tsx
+│ │ └── ReviewDashboard.test.tsx
+│ │
+│ └── e2e/ # Playwright E2E
+│ ├── creator-flow.spec.ts
+│ ├── agency-flow.spec.ts
+│ └── brand-flow.spec.ts
+│
+├── vitest.config.ts # Vitest 配置
+├── playwright.config.ts # Playwright 配置
+└── package.json
+```
+
+### 5.3 单元测试示例
+
+#### 5.3.1 工具函数测试
+
+```typescript
+// src/lib/utils.test.ts
+
+import { describe, it, expect } from 'vitest'
+import {
+ formatDuration,
+ formatTimestamp,
+ truncateText,
+ validateVideoFile
+} from './utils'
+
+describe('formatDuration', () => {
+ it('格式化秒数为 mm:ss', () => {
+ expect(formatDuration(65)).toBe('01:05')
+ expect(formatDuration(3661)).toBe('61:01')
+ expect(formatDuration(0)).toBe('00:00')
+ })
+
+ it('处理负数', () => {
+ expect(formatDuration(-10)).toBe('00:00')
+ })
+})
+
+describe('formatTimestamp', () => {
+ it('格式化毫秒为 HH:MM:SS.mmm', () => {
+ expect(formatTimestamp(1500)).toBe('00:00:01.500')
+ expect(formatTimestamp(3661500)).toBe('01:01:01.500')
+ })
+})
+
+describe('validateVideoFile', () => {
+ it('接受有效的 MP4 文件', () => {
+ const file = new File([''], 'test.mp4', { type: 'video/mp4' })
+ Object.defineProperty(file, 'size', { value: 50 * 1024 * 1024 }) // 50MB
+
+ const result = validateVideoFile(file)
+
+ expect(result.valid).toBe(true)
+ })
+
+ it('拒绝超过 100MB 的文件', () => {
+ const file = new File([''], 'test.mp4', { type: 'video/mp4' })
+ Object.defineProperty(file, 'size', { value: 150 * 1024 * 1024 }) // 150MB
+
+ const result = validateVideoFile(file)
+
+ expect(result.valid).toBe(false)
+ expect(result.error).toContain('100MB')
+ })
+
+ it('拒绝非视频格式', () => {
+ const file = new File([''], 'test.pdf', { type: 'application/pdf' })
+
+ const result = validateVideoFile(file)
+
+ expect(result.valid).toBe(false)
+ expect(result.error).toContain('格式')
+ })
+})
+```
+
+#### 5.3.2 React Hook 测试
+
+```typescript
+// src/hooks/useAuth.test.ts
+
+import { renderHook, act, waitFor } from '@testing-library/react'
+import { describe, it, expect, beforeEach, vi } from 'vitest'
+import { useAuth } from './useAuth'
+import { server } from '../tests/mocks/server'
+import { rest } from 'msw'
+
+describe('useAuth', () => {
+ beforeEach(() => {
+ localStorage.clear()
+ })
+
+ it('初始状态为未登录', () => {
+ const { result } = renderHook(() => useAuth())
+
+ expect(result.current.isAuthenticated).toBe(false)
+ expect(result.current.user).toBeNull()
+ })
+
+ it('登录成功后更新状态', async () => {
+ const { result } = renderHook(() => useAuth())
+
+ await act(async () => {
+ await result.current.login('test@example.com', 'password')
+ })
+
+ await waitFor(() => {
+ expect(result.current.isAuthenticated).toBe(true)
+ expect(result.current.user?.email).toBe('test@example.com')
+ })
+ })
+
+ it('登录失败时抛出错误', async () => {
+ // 模拟 API 返回错误
+ server.use(
+ rest.post('/api/auth/login', (req, res, ctx) => {
+ return res(ctx.status(401), ctx.json({ error: '密码错误' }))
+ })
+ )
+
+ const { result } = renderHook(() => useAuth())
+
+ await expect(
+ act(async () => {
+ await result.current.login('test@example.com', 'wrong')
+ })
+ ).rejects.toThrow('密码错误')
+ })
+
+ it('登出后清除状态', async () => {
+ const { result } = renderHook(() => useAuth())
+
+ // 先登录
+ await act(async () => {
+ await result.current.login('test@example.com', 'password')
+ })
+
+ // 再登出
+ act(() => {
+ result.current.logout()
+ })
+
+ expect(result.current.isAuthenticated).toBe(false)
+ expect(result.current.user).toBeNull()
+ })
+})
+```
+
+#### 5.3.3 Zustand 状态测试
+
+```typescript
+// src/store/upload.test.ts
+
+import { describe, it, expect, beforeEach } from 'vitest'
+import { useUploadStore } from './upload'
+
+describe('useUploadStore', () => {
+ beforeEach(() => {
+ // 重置 store
+ useUploadStore.setState({
+ files: [],
+ uploadProgress: {},
+ isUploading: false,
+ })
+ })
+
+ it('添加文件到上传队列', () => {
+ const file = new File(['test'], 'test.mp4', { type: 'video/mp4' })
+
+ useUploadStore.getState().addFile(file)
+
+ expect(useUploadStore.getState().files).toHaveLength(1)
+ expect(useUploadStore.getState().files[0].name).toBe('test.mp4')
+ })
+
+ it('更新上传进度', () => {
+ const fileId = 'file-123'
+
+ useUploadStore.getState().updateProgress(fileId, 50)
+
+ expect(useUploadStore.getState().uploadProgress[fileId]).toBe(50)
+ })
+
+ it('移除已完成的文件', () => {
+ const file = new File(['test'], 'test.mp4', { type: 'video/mp4' })
+ useUploadStore.getState().addFile(file)
+
+ const fileId = useUploadStore.getState().files[0].id
+ useUploadStore.getState().removeFile(fileId)
+
+ expect(useUploadStore.getState().files).toHaveLength(0)
+ })
+})
+```
+
+#### 5.3.4 组件测试
+
+```typescript
+// src/components/video/VideoUpload.test.tsx
+
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, it, expect, vi } from 'vitest'
+import { VideoUpload } from './VideoUpload'
+
+describe('VideoUpload', () => {
+ it('渲染上传区域', () => {
+ render()
+
+ expect(screen.getByText(/拖拽视频到此处/)).toBeInTheDocument()
+ expect(screen.getByText(/支持 MP4、MOV 格式/)).toBeInTheDocument()
+ })
+
+ it('拖拽文件触发上传', async () => {
+ const onUpload = vi.fn()
+ render()
+
+ const dropzone = screen.getByTestId('dropzone')
+ const file = new File(['video content'], 'test.mp4', { type: 'video/mp4' })
+
+ fireEvent.drop(dropzone, {
+ dataTransfer: { files: [file] }
+ })
+
+ await waitFor(() => {
+ expect(onUpload).toHaveBeenCalledWith(file)
+ })
+ })
+
+ it('拒绝超大文件并显示错误', async () => {
+ const onUpload = vi.fn()
+ render()
+
+ const file = new File([''], 'large.mp4', { type: 'video/mp4' })
+ Object.defineProperty(file, 'size', { value: 150 * 1024 * 1024 })
+
+ const dropzone = screen.getByTestId('dropzone')
+ fireEvent.drop(dropzone, {
+ dataTransfer: { files: [file] }
+ })
+
+ await waitFor(() => {
+ expect(screen.getByText(/文件大小不能超过 100MB/)).toBeInTheDocument()
+ expect(onUpload).not.toHaveBeenCalled()
+ })
+ })
+
+ it('显示上传进度', async () => {
+ render()
+
+ expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '45')
+ expect(screen.getByText('45%')).toBeInTheDocument()
+ })
+})
+```
+
+### 5.4 MSW Mock 服务配置
+
+```typescript
+// tests/mocks/handlers.ts
+
+import { rest } from 'msw'
+
+export const handlers = [
+ // 认证 API
+ rest.post('/api/auth/login', async (req, res, ctx) => {
+ const { email, password } = await req.json()
+
+ if (password === 'password') {
+ return res(ctx.json({
+ access_token: 'mock-token',
+ user: { id: '1', email, role: 'agency' }
+ }))
+ }
+
+ return res(ctx.status(401), ctx.json({ error: '密码错误' }))
+ }),
+
+ // Brief API
+ rest.get('/api/v1/briefs/:id', (req, res, ctx) => {
+ return res(ctx.json({
+ id: req.params.id,
+ selling_points: ['24小时持妆', '天然成分'],
+ prohibited_words: ['药用', '治疗'],
+ status: 'parsed'
+ }))
+ }),
+
+ // 视频上传 API
+ rest.post('/api/v1/videos/upload', async (req, res, ctx) => {
+ return res(ctx.json({
+ task_id: 'mock-task-id',
+ status: 'processing'
+ }))
+ }),
+
+ // WebSocket 模拟
+ // 注意:MSW 不支持 WebSocket,需要单独的 mock
+]
+```
+
+```typescript
+// tests/mocks/server.ts
+
+import { setupServer } from 'msw/node'
+import { handlers } from './handlers'
+
+export const server = setupServer(...handlers)
+```
+
+```typescript
+// tests/setup.ts
+
+import { beforeAll, afterEach, afterAll } from 'vitest'
+import { server } from './mocks/server'
+import '@testing-library/jest-dom'
+
+beforeAll(() => server.listen())
+afterEach(() => server.resetHandlers())
+afterAll(() => server.close())
+```
+
+### 5.5 前端测试自动化方案总结
+
+| 测试类型 | 工具 | 覆盖范围 | 执行时机 |
+| --- | --- | --- | --- |
+| **单元测试** | Vitest | 工具函数、Hooks、Store | 每次提交 |
+| **组件测试** | Vitest + RTL | UI 组件行为 | 每次提交 |
+| **集成测试** | Vitest + MSW | 页面级交互 | 每次 PR |
+| **E2E 测试** | Playwright | 完整用户流程 | 每日/发布前 |
+| **视觉回归** | Percy/Chromatic | UI 外观变化 | 每次 PR |
+| **兼容性测试** | BrowserStack | 跨浏览器/设备 | 发布前 |
+
+---
+
+## 6. AI 模型测试策略
+
+### 6.1 AI 测试的特殊性
+
+AI 模型测试与传统单元测试有本质区别:
+
+| 维度 | 传统测试 | AI 模型测试 |
+| --- | --- | --- |
+| **输出确定性** | 确定性输出 | 概率性输出 |
+| **验证方式** | 精确匹配 | 阈值验证 |
+| **测试数据** | 少量手工构造 | 大规模标注集 |
+| **回归检测** | 断言失败 | 指标下降 |
+| **维护成本** | 低 | 需持续更新 |
+
+### 6.2 AI 测试分层
+
+```
+┌────────────────────────────────────────────────────────────────┐
+│ AI 模型测试分层 │
+├────────────────────────────────────────────────────────────────┤
+│ │
+│ 【第1层:接口契约测试】 │
+│ • 输入格式验证 │
+│ • 输出结构验证 │
+│ • 错误处理验证 │
+│ → 使用 pytest + JSON Schema 验证 │
+│ │
+│ 【第2层:功能正确性测试】 │
+│ • 标注测试集验证 │
+│ • 边界情况覆盖 │
+│ • 阈值达标检查 │
+│ → 使用标注数据 + 指标计算 │
+│ │
+│ 【第3层:回归测试】 │
+│ • 模型更新后的指标对比 │
+│ • Prompt 修改后的行为验证 │
+│ • 新增 Case 的持续覆盖 │
+│ → 使用 MLflow + 版本对比 │
+│ │
+│ 【第4层:对抗测试】 │
+│ • 边缘输入 (长文本、特殊字符、空输入) │
+│ • 对抗样本 (刻意绕过检测) │
+│ • 压力测试 (高并发、大文件) │
+│ → 使用 fuzzing + 人工设计 │
+│ │
+└────────────────────────────────────────────────────────────────┘
+```
+
+### 6.3 各 AI 模块测试策略
+
+#### 6.3.1 ASR 语音识别测试
+
+```python
+# tests/ai/test_asr.py
+
+import pytest
+from app.ai.asr import ASRService
+
+class TestASRService:
+ """ASR 语音识别测试"""
+
+ @pytest.fixture
+ def asr(self):
+ return ASRService()
+
+ @pytest.fixture
+ def test_audio_samples(self):
+ """加载标注测试集"""
+ return load_labeled_dataset("tests/fixtures/asr_samples/")
+
+ def test_output_format(self, asr):
+ """验证输出格式契约"""
+ result = asr.transcribe("tests/fixtures/sample.wav")
+
+ # 验证必需字段
+ assert "text" in result
+ assert "timestamps" in result
+ assert isinstance(result["timestamps"], list)
+
+ # 验证时间戳格式
+ for ts in result["timestamps"]:
+ assert "start" in ts
+ assert "end" in ts
+ assert "text" in ts
+ assert ts["end"] >= ts["start"]
+
+ def test_word_error_rate(self, asr, test_audio_samples):
+ """验证字错率 ≤ 10%"""
+ total_errors = 0
+ total_words = 0
+
+ for sample in test_audio_samples:
+ result = asr.transcribe(sample["audio_path"])
+ wer = calculate_wer(result["text"], sample["ground_truth"])
+ total_errors += wer * len(sample["ground_truth"].split())
+ total_words += len(sample["ground_truth"].split())
+
+ overall_wer = total_errors / total_words
+
+ assert overall_wer <= 0.10, f"WER {overall_wer:.2%} 超过阈值 10%"
+
+ def test_timestamp_accuracy(self, asr, test_audio_samples):
+ """验证时间戳准确性"""
+ for sample in test_audio_samples:
+ result = asr.transcribe(sample["audio_path"])
+
+ # 验证起止时间与音频时长匹配
+ audio_duration = get_audio_duration(sample["audio_path"])
+ last_timestamp = result["timestamps"][-1]["end"]
+
+ # 允许 500ms 误差
+ assert abs(last_timestamp - audio_duration * 1000) <= 500
+```
+
+#### 6.3.2 违禁词检测测试
+
+```python
+# tests/ai/test_prohibited_words.py
+
+import pytest
+from app.ai.nlp import ProhibitedWordDetector
+
+class TestProhibitedWordDetector:
+ """违禁词检测测试"""
+
+ @pytest.fixture
+ def detector(self):
+ return ProhibitedWordDetector()
+
+ @pytest.fixture
+ def labeled_dataset(self):
+ """
+ 标注数据集格式:
+ {
+ "text": "这是全网销量第一的产品",
+ "context": "advertisement",
+ "expected_violations": ["全网第一"],
+ "should_block": true
+ }
+ """
+ return load_labeled_dataset("tests/fixtures/prohibited_words/")
+
+ def test_recall_rate(self, detector, labeled_dataset):
+ """验证召回率 ≥ 95%"""
+ true_positives = 0
+ false_negatives = 0
+
+ for sample in labeled_dataset:
+ if not sample["expected_violations"]:
+ continue
+
+ result = detector.detect(sample["text"], sample["context"])
+ detected = set(result.violations)
+ expected = set(sample["expected_violations"])
+
+ true_positives += len(detected & expected)
+ false_negatives += len(expected - detected)
+
+ recall = true_positives / (true_positives + false_negatives)
+
+ assert recall >= 0.95, f"召回率 {recall:.2%} 低于阈值 95%"
+
+ def test_false_positive_rate(self, detector, labeled_dataset):
+ """验证误报率 ≤ 5%"""
+ false_positives = 0
+ true_negatives = 0
+
+ # 只测试不应有违规的样本
+ negative_samples = [
+ s for s in labeled_dataset
+ if not s["expected_violations"]
+ ]
+
+ for sample in negative_samples:
+ result = detector.detect(sample["text"], sample["context"])
+
+ if result.violations:
+ false_positives += 1
+ else:
+ true_negatives += 1
+
+ fpr = false_positives / (false_positives + true_negatives)
+
+ assert fpr <= 0.05, f"误报率 {fpr:.2%} 超过阈值 5%"
+
+ def test_context_awareness(self, detector):
+ """验证语境感知能力"""
+ text = "这是我最开心的一天"
+
+ # 广告语境 - 不应误报
+ result_ad = detector.detect(text, context="advertisement")
+ assert len(result_ad.violations) == 0, "日常用语在广告语境误报"
+
+ # 日常语境 - 不应误报
+ result_daily = detector.detect(text, context="daily_conversation")
+ assert len(result_daily.violations) == 0, "日常用语误报"
+```
+
+#### 6.3.3 Logo 向量检索测试
+
+```python
+# tests/ai/test_logo_detection.py
+
+import pytest
+from app.ai.cv import LogoDetector
+
+class TestLogoDetector:
+ """Logo 检测测试"""
+
+ @pytest.fixture
+ def detector(self):
+ return LogoDetector()
+
+ @pytest.fixture
+ def logo_test_set(self):
+ """
+ 测试集包含:
+ - 200+ 竞品 Logo 图片
+ - 各种遮挡、模糊、旋转场景
+ - 负样本(无 Logo 的图片)
+ """
+ return load_labeled_dataset("tests/fixtures/logos/")
+
+ def test_f1_score(self, detector, logo_test_set):
+ """验证 F1 ≥ 0.85"""
+ predictions = []
+ ground_truths = []
+
+ for sample in logo_test_set:
+ result = detector.detect(sample["image_path"])
+ predictions.append(result.detected_logos)
+ ground_truths.append(sample["ground_truth_logos"])
+
+ f1 = calculate_f1(predictions, ground_truths)
+
+ assert f1 >= 0.85, f"F1 {f1:.2f} 低于阈值 0.85"
+
+ def test_partial_occlusion(self, detector, logo_test_set):
+ """验证 30% 遮挡场景下的检测能力"""
+ occluded_samples = [
+ s for s in logo_test_set
+ if s.get("occlusion_rate", 0) >= 0.3
+ ]
+
+ correct = 0
+ for sample in occluded_samples:
+ result = detector.detect(sample["image_path"])
+ if sample["ground_truth_logos"] == result.detected_logos:
+ correct += 1
+
+ accuracy = correct / len(occluded_samples)
+
+ # 遮挡场景允许稍低的准确率
+ assert accuracy >= 0.75, f"遮挡场景准确率 {accuracy:.2%} 过低"
+
+ def test_new_logo_instant_detection(self, detector):
+ """验证新 Logo 上传后即刻生效"""
+ # 上传新 Logo
+ new_logo_path = "tests/fixtures/new_competitor_logo.png"
+ detector.add_logo(new_logo_path, brand="New Competitor")
+
+ # 立即测试检测
+ test_frame = "tests/fixtures/frame_with_new_logo.jpg"
+ result = detector.detect(test_frame)
+
+ assert "New Competitor" in result.detected_logos
+```
+
+### 6.4 LLM Prompt 测试
+
+```python
+# tests/ai/test_llm.py
+
+import pytest
+from app.ai.llm import LLMService
+
+class TestLLMPrompts:
+ """LLM Prompt 测试"""
+
+ @pytest.fixture
+ def llm(self):
+ return LLMService()
+
+ @pytest.fixture
+ def few_shot_examples(self):
+ """Few-shot 示例集"""
+ return load_few_shot_examples("tests/fixtures/llm_examples/")
+
+ def test_brief_parsing_output_format(self, llm):
+ """验证 Brief 解析输出格式"""
+ brief_content = """
+ 产品卖点:24小时持妆
+ 禁止使用:药用、治疗
+ """
+
+ result = llm.parse_brief(brief_content)
+
+ # 验证输出结构
+ assert "selling_points" in result
+ assert "prohibited_words" in result
+ assert isinstance(result["selling_points"], list)
+ assert isinstance(result["prohibited_words"], list)
+
+ def test_context_understanding(self, llm, few_shot_examples):
+ """验证语境理解能力"""
+ context_examples = [
+ e for e in few_shot_examples
+ if e["type"] == "context_understanding"
+ ]
+
+ correct = 0
+ for example in context_examples:
+ result = llm.classify_context(example["text"])
+ if result["context"] == example["expected_context"]:
+ correct += 1
+
+ accuracy = correct / len(context_examples)
+
+ assert accuracy >= 0.90, f"语境理解准确率 {accuracy:.2%} 过低"
+
+ def test_sentiment_analysis(self, llm):
+ """验证舆情风险检测"""
+ test_cases = [
+ {"text": "这个产品太油腻了", "expected_risk": "greasy"},
+ {"text": "正常的产品介绍", "expected_risk": None},
+ {"text": "男人就该这样", "expected_risk": "gender_bias"},
+ ]
+
+ for case in test_cases:
+ result = llm.analyze_sentiment(case["text"])
+
+ if case["expected_risk"]:
+ assert result.risk_type == case["expected_risk"]
+ else:
+ assert result.risk_type is None
+```
+
+### 6.5 AI 测试数据集管理
+
+```
+tests/fixtures/
+├── asr_samples/ # ASR 测试集
+│ ├── manifest.json # 数据清单
+│ ├── audio/
+│ │ ├── sample_001.wav
+│ │ └── ...
+│ └── transcripts/
+│ ├── sample_001.json # 标注结果
+│ └── ...
+│
+├── prohibited_words/ # 违禁词测试集
+│ ├── positive_samples.json # 应检出样本
+│ ├── negative_samples.json # 不应检出样本
+│ └── context_samples.json # 语境测试样本
+│
+├── logos/ # Logo 测试集
+│ ├── manifest.json
+│ ├── images/
+│ │ ├── logo_001.jpg
+│ │ └── ...
+│ └── annotations/
+│ ├── logo_001.json
+│ └── ...
+│
+├── llm_examples/ # LLM 测试集
+│ ├── brief_parsing.json
+│ ├── context_understanding.json
+│ └── sentiment_analysis.json
+│
+└── README.md # 测试集说明文档
+```
+
+---
+
+## 7. 端到端测试策略
+
+### 7.1 E2E 测试框架
+
+| 工具 | 用途 |
+| --- | --- |
+| **Playwright** | 跨浏览器 E2E 测试 |
+| **@playwright/test** | 测试运行器 |
+| **playwright-report** | 测试报告 |
+| **BrowserStack** | 真机云测试 |
+
+### 7.2 核心用户流程测试
+
+```typescript
+// tests/e2e/creator-flow.spec.ts
+
+import { test, expect } from '@playwright/test'
+
+test.describe('达人端完整流程', () => {
+ test.beforeEach(async ({ page }) => {
+ // 登录达人账号
+ await page.goto('/auth/login')
+ await page.fill('[name="email"]', 'creator@test.com')
+ await page.fill('[name="password"]', 'password')
+ await page.click('button[type="submit"]')
+ await expect(page).toHaveURL('/creator/tasks')
+ })
+
+ test('上传视频 → 等待审核 → 查看结果', async ({ page }) => {
+ // 1. 进入上传页面
+ await page.click('text=上传')
+ await expect(page).toHaveURL('/creator/upload')
+
+ // 2. 上传视频
+ const fileInput = page.locator('input[type="file"]')
+ await fileInput.setInputFiles('tests/fixtures/test_video.mp4')
+
+ // 3. 等待上传完成
+ await expect(page.locator('.upload-progress')).toHaveText(/100%/)
+
+ // 4. 等待审核完成(可能需要等待)
+ await page.click('button:has-text("提交审核")')
+
+ // 5. 验证进入审核中状态
+ await expect(page.locator('.audit-status')).toHaveText(/审核中/)
+
+ // 6. 等待审核完成(最多 5 分钟)
+ await expect(page.locator('.audit-status')).toHaveText(
+ /已通过|需修改/,
+ { timeout: 300000 }
+ )
+
+ // 7. 验证结果页面
+ await page.click('text=查看结果')
+ await expect(page.locator('.result-banner')).toBeVisible()
+ })
+
+ test('申诉流程', async ({ page }) => {
+ // 假设有一个需修改的任务
+ await page.goto('/creator/tasks?status=needs_revision')
+ await page.click('.task-card >> nth=0')
+
+ // 1. 点击申诉按钮
+ await page.click('button:has-text("申诉")')
+
+ // 2. 填写申诉理由
+ await page.fill('textarea[name="reason"]', '这不是广告用语,是日常表达')
+
+ // 3. 提交申诉
+ await page.click('button:has-text("提交申诉")')
+
+ // 4. 验证申诉成功
+ await expect(page.locator('.toast')).toHaveText(/申诉已提交/)
+ })
+})
+```
+
+```typescript
+// tests/e2e/agency-flow.spec.ts
+
+import { test, expect } from '@playwright/test'
+
+test.describe('代理商端完整流程', () => {
+ test.beforeEach(async ({ page }) => {
+ // 登录代理商账号
+ await page.goto('/auth/login')
+ await page.fill('[name="email"]', 'agency@test.com')
+ await page.fill('[name="password"]', 'password')
+ await page.click('button[type="submit"]')
+ await expect(page).toHaveURL('/agency/dashboard')
+ })
+
+ test('配置 Brief → 审核视频 → 通过', async ({ page }) => {
+ // 1. 上传 Brief
+ await page.click('text=Brief 管理')
+ await page.click('button:has-text("上传 Brief")')
+
+ const fileInput = page.locator('input[type="file"]')
+ await fileInput.setInputFiles('tests/fixtures/test_brief.pdf')
+
+ // 2. 等待解析完成
+ await expect(page.locator('.parsing-status')).toHaveText(/解析完成/, {
+ timeout: 60000
+ })
+
+ // 3. 确认规则
+ await page.click('button:has-text("确认规则")')
+
+ // 4. 进入审核台
+ await page.click('text=审核台')
+ await page.click('.pending-task >> nth=0')
+
+ // 5. 查看视频和检查单
+ await expect(page.locator('.video-player')).toBeVisible()
+ await expect(page.locator('.checklist')).toBeVisible()
+
+ // 6. 通过审核
+ await page.click('button:has-text("通过")')
+ await page.click('button:has-text("确认")')
+
+ // 7. 验证状态更新
+ await expect(page.locator('.task-status')).toHaveText(/已通过/)
+ })
+
+ test('驳回视频', async ({ page }) => {
+ await page.goto('/agency/review')
+ await page.click('.pending-task >> nth=0')
+
+ // 勾选问题
+ await page.check('input[name="issue_0"]')
+ await page.check('input[name="issue_1"]')
+
+ // 驳回
+ await page.click('button:has-text("驳回")')
+ await page.click('button:has-text("确认")')
+
+ await expect(page.locator('.task-status')).toHaveText(/已驳回/)
+ })
+})
+```
+
+### 7.3 移动端 E2E 测试
+
+```typescript
+// tests/e2e/mobile-creator.spec.ts
+
+import { test, expect, devices } from '@playwright/test'
+
+test.use({
+ ...devices['iPhone 13'],
+})
+
+test.describe('达人端 H5 移动端测试', () => {
+ test('移动端上传视频', async ({ page }) => {
+ await page.goto('/creator/upload')
+
+ // 验证移动端布局
+ await expect(page.locator('.bottom-nav')).toBeVisible()
+
+ // 验证防锁屏提示
+ await expect(page.locator('.wakelock-hint')).toBeVisible()
+
+ // 模拟上传
+ const fileInput = page.locator('input[type="file"]')
+ await fileInput.setInputFiles('tests/fixtures/test_video.mp4')
+
+ // 验证进度显示
+ await expect(page.locator('.circular-progress')).toBeVisible()
+ })
+})
+```
+
+### 7.4 Playwright 配置
+
+```typescript
+// playwright.config.ts
+
+import { defineConfig, devices } from '@playwright/test'
+
+export default defineConfig({
+ testDir: './tests/e2e',
+ timeout: 60000,
+ retries: 2,
+ workers: 4,
+
+ reporter: [
+ ['html', { outputFolder: 'playwright-report' }],
+ ['junit', { outputFile: 'test-results/junit.xml' }],
+ ],
+
+ use: {
+ baseURL: process.env.TEST_BASE_URL || 'http://localhost:3000',
+ trace: 'on-first-retry',
+ screenshot: 'only-on-failure',
+ video: 'on-first-retry',
+ },
+
+ projects: [
+ // 桌面浏览器
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
+
+ // 移动端
+ { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
+ { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
+ ],
+
+ // 本地开发服务器
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:3000',
+ reuseExistingServer: !process.env.CI,
+ },
+})
+```
+
+---
+
+## 8. 实施路线图
+
+### 8.1 分阶段实施计划
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ TDD 实施路线图 (11 周) │
+├──────────────────────────────────────────────────────────────────────┤
+│ │
+│ Phase 0: 基础建设 (Week 0, 并行进行) │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ • 搭建 pytest/Vitest 测试框架 │ │
+│ │ • 配置 CI/CD 测试流水线 │ │
+│ │ • 建立代码覆盖率门禁 │ │
+│ │ • 准备 AI 测试数据集 (初始 100+ 样本) │ │
+│ │ • 团队 TDD 培训 │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ Phase 1: 基础设施 + Brief 引擎 (Week 1-2) │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ 后端 TDD (100% 覆盖): │ │
+│ │ • 数据模型测试 → 数据模型实现 │ │
+│ │ • 验证器测试 → 验证器实现 │ │
+│ │ • 规则引擎测试 → 规则引擎实现 │ │
+│ │ • Brief 解析测试 → Brief 解析实现 │ │
+│ │ │ │
+│ │ 前端 TDD (基础组件): │ │
+│ │ • 工具函数测试 → 工具函数实现 │ │
+│ │ • 基础组件测试 → 基础组件实现 │ │
+│ │ │ │
+│ │ API Mock 服务搭建 │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ Phase 2: 核心 AI 流水线 (Week 3-6) │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ AI 模型测试 (标注集验证): │ │
+│ │ • 建立 ASR/OCR/CV 测试集 (≥500 样本) │ │
+│ │ • 接口契约测试 → AI 服务封装 │ │
+│ │ • 阈值验证测试 → 模型调优 │ │
+│ │ │ │
+│ │ 核心算法 TDD: │ │
+│ │ • 时间戳对齐测试 → 对齐算法实现 │ │
+│ │ • 多模态融合测试 → 融合逻辑实现 │ │
+│ │ │ │
+│ │ 集成测试: │ │
+│ │ • Celery 任务测试 │ │
+│ │ • WebSocket 推送测试 │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ Phase 3: 界面开发 (Week 7-9) │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ 前端组件 TDD: │ │
+│ │ • 组件测试 → 组件实现 │ │
+│ │ • Hook 测试 → Hook 实现 │ │
+│ │ • Store 测试 → Store 实现 │ │
+│ │ │ │
+│ │ 页面集成测试: │ │
+│ │ • MSW Mock + 页面交互测试 │ │
+│ │ │ │
+│ │ E2E 测试骨架: │ │
+│ │ • 核心用户流程 E2E (Playwright) │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ ↓ │
+│ Phase 4: 联调与验收 (Week 10-11) │
+│ ┌────────────────────────────────────────────────────────────────┐ │
+│ │ 测试完善: │ │
+│ │ • E2E 测试补全 │ │
+│ │ • 性能测试 (Locust) │ │
+│ │ • 兼容性测试 (BrowserStack) │ │
+│ │ │ │
+│ │ AI 模型验收: │ │
+│ │ • 完整测试集运行 │ │
+│ │ • 指标达标验证 │ │
+│ │ │ │
+│ │ 回归测试: │ │
+│ │ • 全量回归 │ │
+│ │ • 冒烟测试自动化 │ │
+│ └────────────────────────────────────────────────────────────────┘ │
+│ │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+### 8.2 每周测试交付物
+
+| 周次 | 测试交付物 | 覆盖率目标 |
+| --- | --- | --- |
+| Week 1 | 后端框架测试、数据模型测试 | 后端 80% |
+| Week 2 | Brief 解析测试、规则引擎测试 | 后端 80% |
+| Week 3 | ASR/OCR 接口测试 | AI 模块 60% |
+| Week 4 | CV 检测测试、向量检索测试 | AI 模块 70% |
+| Week 5 | 时间戳对齐测试、多模态融合测试 | AI 模块 80% |
+| Week 6 | Celery 任务测试、WebSocket 测试 | 后端 85% |
+| Week 7 | 前端工具函数测试、Hook 测试 | 前端 60% |
+| Week 8 | 前端组件测试、Store 测试 | 前端 70% |
+| Week 9 | 页面集成测试、E2E 骨架 | 前端 75% |
+| Week 10 | E2E 补全、性能测试 | E2E 核心路径 100% |
+| Week 11 | 兼容性测试、全量回归 | 整体 75% |
+
+---
+
+## 9. 测试覆盖率目标
+
+### 9.1 覆盖率门禁
+
+| 层级 | 目标覆盖率 | 门禁策略 |
+| --- | --- | --- |
+| **后端单元测试** | ≥ 80% | PR 阻断 |
+| **前端单元测试** | ≥ 70% | PR 阻断 |
+| **AI 模块测试** | ≥ 70% | PR 阻断 |
+| **集成测试** | ≥ 60% | PR 警告 |
+| **E2E 测试** | 核心路径 100% | 发布阻断 |
+
+### 9.2 覆盖率例外
+
+以下代码可豁免覆盖率要求:
+
+| 代码类型 | 原因 |
+| --- | --- |
+| 第三方 SDK 封装 | 信任上游 |
+| 环境配置代码 | 运行时验证 |
+| 日志/监控代码 | 非核心逻辑 |
+| 迁移脚本 | 一次性执行 |
+
+### 9.3 覆盖率报告
+
+```yaml
+# .github/workflows/test.yml (覆盖率报告部分)
+
+- name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ files: ./coverage/coverage.xml
+ fail_ci_if_error: true
+
+- name: Coverage Gate
+ run: |
+ COVERAGE=$(cat coverage/coverage.txt | grep "TOTAL" | awk '{print $4}' | tr -d '%')
+ if [ "$COVERAGE" -lt "75" ]; then
+ echo "Coverage $COVERAGE% is below threshold 75%"
+ exit 1
+ fi
+```
+
+---
+
+## 10. 工具链配置
+
+### 10.1 后端工具链
+
+```toml
+# pyproject.toml
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["test_*.py"]
+python_functions = ["test_*"]
+addopts = [
+ "-v",
+ "--tb=short",
+ "--strict-markers",
+ "-ra",
+ "--cov=app",
+ "--cov-report=xml",
+ "--cov-report=html",
+ "--cov-fail-under=75",
+]
+asyncio_mode = "auto"
+markers = [
+ "slow: 标记慢速测试",
+ "integration: 集成测试",
+ "ai: AI 模型测试",
+]
+
+[tool.coverage.run]
+branch = true
+source = ["app"]
+omit = [
+ "*/migrations/*",
+ "*/tests/*",
+ "*/__init__.py",
+]
+
+[tool.coverage.report]
+exclude_lines = [
+ "pragma: no cover",
+ "def __repr__",
+ "raise NotImplementedError",
+ "if TYPE_CHECKING:",
+]
+```
+
+### 10.2 前端工具链
+
+```typescript
+// vitest.config.ts
+
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import path from 'path'
+
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: ['./tests/setup.ts'],
+ include: ['**/*.test.{ts,tsx}'],
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: [
+ 'node_modules/',
+ 'tests/',
+ '**/*.d.ts',
+ '**/*.config.*',
+ ],
+ thresholds: {
+ lines: 70,
+ functions: 70,
+ branches: 70,
+ statements: 70,
+ },
+ },
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+})
+```
+
+### 10.3 CI/CD 配置
+
+```yaml
+# .github/workflows/test.yml
+
+name: Test Suite
+
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
+
+jobs:
+ backend-test:
+ runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:15
+ env:
+ POSTGRES_PASSWORD: test
+ ports:
+ - 5432:5432
+ redis:
+ image: redis:7
+ ports:
+ - 6379:6379
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+ cache: 'pip'
+
+ - name: Install dependencies
+ run: |
+ cd backend
+ pip install -r requirements.txt
+ pip install -r requirements-dev.txt
+
+ - name: Run linting
+ run: |
+ cd backend
+ ruff check .
+ mypy app
+
+ - name: Run tests
+ run: |
+ cd backend
+ pytest --cov --cov-report=xml
+ env:
+ DATABASE_URL: postgresql://postgres:test@localhost/test
+ REDIS_URL: redis://localhost:6379
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v3
+ with:
+ files: backend/coverage.xml
+
+ frontend-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: Install dependencies
+ run: |
+ cd frontend
+ npm ci
+
+ - name: Run linting
+ run: |
+ cd frontend
+ npm run lint
+
+ - name: Run tests
+ run: |
+ cd frontend
+ npm run test:coverage
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v3
+ with:
+ files: frontend/coverage/coverage-final.json
+
+ e2e-test:
+ runs-on: ubuntu-latest
+ needs: [backend-test, frontend-test]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install Playwright
+ run: |
+ cd frontend
+ npm ci
+ npx playwright install --with-deps
+
+ - name: Run E2E tests
+ run: |
+ cd frontend
+ npm run test:e2e
+
+ - name: Upload Playwright report
+ uses: actions/upload-artifact@v3
+ if: failure()
+ with:
+ name: playwright-report
+ path: frontend/playwright-report/
+```
+
+---
+
+## 11. 团队规范与培训
+
+### 11.1 TDD 工作流规范
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ TDD 红-绿-重构循环 │
+├────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────┐ │
+│ │ 🔴 RED │ 1. 编写一个失败的测试 │
+│ │ (失败) │ • 测试必须能运行 │
+│ │ │ • 测试必须失败 │
+│ └──────┬──────┘ • 失败原因是功能未实现 │
+│ │ │
+│ ▼ │
+│ ┌─────────────┐ │
+│ │ 🟢 GREEN │ 2. 编写最少的代码让测试通过 │
+│ │ (通过) │ • 不要过度设计 │
+│ │ │ • 只写足够通过测试的代码 │
+│ └──────┬──────┘ • 可以"作弊"(硬编码) │
+│ │ │
+│ ▼ │
+│ ┌─────────────┐ │
+│ │ 🔄 REFACTOR │ 3. 重构代码 │
+│ │ (重构) │ • 移除重复 │
+│ │ │ • 改善设计 │
+│ └──────┬──────┘ • 测试仍然通过 │
+│ │ │
+│ └──────────────────────────────────────────────────────► │
+│ 循环 │
+│ │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+### 11.2 测试命名规范
+
+```python
+# Python (pytest)
+
+class TestCalculator:
+ def test_add_two_positive_numbers_returns_sum(self):
+ """测试两个正数相加返回正确的和"""
+ pass
+
+ def test_divide_by_zero_raises_error(self):
+ """测试除以零抛出错误"""
+ pass
+```
+
+```typescript
+// TypeScript (Vitest)
+
+describe('Calculator', () => {
+ it('should return sum when adding two positive numbers', () => {
+ // ...
+ })
+
+ it('should throw error when dividing by zero', () => {
+ // ...
+ })
+})
+```
+
+### 11.3 测试文件组织规范
+
+| 规则 | 说明 |
+| --- | --- |
+| 测试文件与源文件同目录 | `utils.ts` → `utils.test.ts` |
+| 测试目录 `__tests__` | 复杂模块可用目录 |
+| 命名后缀 `.test.ts` / `_test.py` | 便于识别和自动发现 |
+| 每个测试文件只测一个模块 | 职责单一 |
+
+### 11.4 团队培训计划
+
+| 阶段 | 时长 | 内容 | 交付物 |
+| --- | --- | --- | --- |
+| **TDD 基础** | 2h | TDD 概念、红绿重构循环 | 培训 PPT |
+| **pytest 实战** | 2h | pytest 使用、fixture、参数化 | 示例代码 |
+| **Vitest 实战** | 2h | Vitest 使用、RTL、MSW | 示例代码 |
+| **AI 测试** | 2h | 标注集管理、阈值验证 | 测试模板 |
+| **代码审查** | 持续 | PR 中检查测试质量 | 审查清单 |
+
+### 11.5 代码审查清单
+
+```markdown
+## PR 测试审查清单
+
+### 必须项
+- [ ] 新功能有对应的单元测试
+- [ ] 测试覆盖了正常路径和异常路径
+- [ ] 测试命名清晰,描述预期行为
+- [ ] 测试独立运行,不依赖执行顺序
+- [ ] 覆盖率不低于门禁阈值
+
+### 建议项
+- [ ] 使用参数化测试覆盖多种输入
+- [ ] Mock 外部依赖,避免测试不稳定
+- [ ] 测试执行时间 < 1秒(单元测试)
+- [ ] 无硬编码的测试数据(使用 fixture)
+
+### AI 模块特别项
+- [ ] 有对应的标注测试集
+- [ ] 验证了输出格式
+- [ ] 验证了阈值指标
+```
+
+---
+
+## 12. 风险与挑战
+
+### 12.1 风险矩阵
+
+| 风险 | 可能性 | 影响 | 缓解措施 |
+| --- | --- | --- | --- |
+| **AI 模型幻觉** | 🔴 高 | 🔴 高 | 完整标注集 + 人工抽查 + 持续监控 |
+| **测试数据不足** | 🟡 中 | 🔴 高 | 持续收集真实数据 + 数据增强 |
+| **E2E 测试不稳定** | 🟡 中 | 🟡 中 | 重试机制 + 等待策略优化 |
+| **团队 TDD 经验不足** | 🟡 中 | 🟡 中 | 培训 + 结对编程 + 代码审查 |
+| **测试维护成本高** | 🟡 中 | 🟡 中 | 测试重构 + 共享 fixture |
+| **CI/CD 执行慢** | 🟢 低 | 🟡 中 | 并行执行 + 增量测试 |
+
+### 12.2 AI 测试特殊挑战
+
+| 挑战 | 应对策略 |
+| --- | --- |
+| **LLM 输出不确定性** | 验证结构而非精确内容 + 多次采样取共识 |
+| **Prompt 变更影响大** | 建立 Prompt 版本管理 + 回归测试 |
+| **标注成本高** | 优先覆盖高风险场景 + 主动学习采样 |
+| **模型更新回归** | 建立基线 + 自动化指标对比 |
+| **边缘情况难穷尽** | 对抗样本生成 + 持续收集 badcase |
+
+### 12.3 TDD 常见误区
+
+| 误区 | 正确做法 |
+| --- | --- |
+| 先写代码再补测试 | 严格遵循红-绿-重构 |
+| 追求 100% 覆盖率 | 关注有意义的测试 |
+| 测试实现细节 | 测试行为和结果 |
+| 过度 Mock | 只 Mock 真正的外部依赖 |
+| 测试代码不维护 | 测试代码同样需要重构 |
+
+---
+
+## 13. 附录
+
+### 13.1 相关文档
+
+| 文档 | 说明 |
+| --- | --- |
+| tasks.md | 开发任务清单 |
+| DevelopmentPlan.md | 技术架构与开发计划 |
+| FeatureSummary.md | 功能清单与验收标准 |
+| User_Role_Interfaces.md | 用户角色与界面规范 |
+
+### 13.2 参考资源
+
+| 资源 | 链接 |
+| --- | --- |
+| pytest 官方文档 | https://docs.pytest.org/ |
+| Vitest 官方文档 | https://vitest.dev/ |
+| Testing Library | https://testing-library.com/ |
+| Playwright 官方文档 | https://playwright.dev/ |
+| MSW 官方文档 | https://mswjs.io/ |
+| TDD by Example (书籍) | Kent Beck |
+
+### 13.3 术语表
+
+| 术语 | 定义 |
+| --- | --- |
+| **TDD** | Test-Driven Development,测试驱动开发 |
+| **BDD** | Behavior-Driven Development,行为驱动开发 |
+| **SUT** | System Under Test,被测系统 |
+| **Fixture** | 测试固定装置,用于准备测试环境 |
+| **Mock** | 模拟对象,替代真实依赖 |
+| **Stub** | 存根,返回预设值的简化实现 |
+| **Coverage** | 代码覆盖率 |
+| **Regression** | 回归测试 |
+
+---
+
+## 14. 总结
+
+### 14.1 核心结论
+
+1. **SmartAudit 项目高度适合实施 TDD**
+ - 零代码起步,是最佳切入点
+ - 需求明确,验收标准量化
+ - 技术栈测试生态成熟
+
+2. **采用分层混合 TDD 策略**
+ - 业务逻辑:严格 TDD
+ - AI 模型:标注集验证
+ - E2E:BDD + 自动化
+
+3. **前端测试自动化方案**
+ - 单元测试:Vitest + Testing Library
+ - 组件测试:RTL + MSW
+ - E2E 测试:Playwright
+ - 视觉回归:Percy/Chromatic
+
+4. **关键成功因素**
+ - 测试框架在 Week 0 搭建完成
+ - CI/CD 门禁从第一行代码开始
+ - AI 测试集持续积累
+ - 团队培训与规范执行
+
+### 14.2 下一步行动
+
+| 优先级 | 行动项 | 负责人 | 时间 |
+| --- | --- | --- | --- |
+| P0 | 创建 backend/tests/ 目录结构 | Backend Lead | Week 0 |
+| P0 | 配置 pytest + CI/CD | Backend Lead | Week 0 |
+| P0 | 创建 frontend/tests/ 目录结构 | Frontend Lead | Week 0 |
+| P0 | 配置 Vitest + Playwright | Frontend Lead | Week 0 |
+| P0 | 团队 TDD 培训 | Tech Lead | Week 0 |
+| P1 | 建立 AI 测试数据集框架 | AI Engineer | Week 1 |
+| P1 | 编写核心模块测试规范文档 | Tech Lead | Week 1 |
+
+---
+
+**文档状态**:✅ 完成
+**下次审阅**:开发启动后 2 周