# GEO 平台 - TDD 流程规范

## 概述

本文档定义 GEO 平台的测试驱动开发（TDD）工作流程。所有功能开发必须遵循 TDD 原则：先写测试，再写实现，最后重构。

TDD 的核心价值：
- **设计先行**：通过测试用例明确需求和接口设计
- **快速反馈**：自动化测试提供即时的正确性验证
- **安全重构**：测试覆盖确保重构不会破坏功能
- **文档即代码**：测试用例本身就是最准确的接口文档

## RED-GREEN-REFACTOR 循环

### 基本循环

```
         ┌──────────────────────┐
         │   1. RED 编写失败测试   │
         │  写一个会失败的测试用例   │
         └───────────┬──────────┘
                     │
                     ▼
         ┌──────────────────────┐
         │   2. GREEN 通过测试    │
         │  写最少代码让测试通过   │
         └───────────┬──────────┘
                     │
                     ▼
         ┌──────────────────────┐
         │  3. REFACTOR 重构代码  │
         │  优化实现，保持测试通过  │
         └───────────┬──────────┘
                     │
                     └──────▶ 下一个循环
```

### 各阶段要求

#### RED 阶段 - 编写失败测试

- **前提**：已完成需求分析和接口设计
- **动作**：编写一个测试用例，验证某个具体功能点
- **验证**：运行测试，确认测试失败（且失败原因符合预期）
- **原则**：
  - 一次只写一个测试
  - 测试命名清晰表达被测行为
  - 使用 Given-When-Then 结构组织测试代码
  - 测试失败信息应清晰说明期望 vs 实际

**示例（Python + pytest）**：
```python
# tests/test_citation_detector.py

class TestCitationDetector:
    def test_should_detect_direct_brand_reference(self):
        """Given: AI 响应中包含品牌直接引用
           When: 执行引用检测
           Then: 返回引用类型为 direct_quote"""
        # Given
        response = "根据 ExampleBrand 的研究报告显示..."
        brand = "ExampleBrand"
        
        detector = CitationDetector()
        
        # When
        result = detector.detect(response, brand)
        
        # Then
        assert result.reference_type == ReferenceType.DIRECT_QUOTE
        assert result.confidence >= 0.8
```

#### GREEN 阶段 - 通过测试

- **前提**：已有一个失败的测试
- **动作**：编写最少量的生产代码使测试通过
- **验证**：运行测试，确认测试通过
- **原则**：
  - 不择手段让测试通过（可以写丑陋的代码）
  - 不要提前实现未测试的功能
  - 如果测试仍然失败，调试直到通过
  - 如果通过后发现测试有问题，返回 RED 阶段

**示例**：
```python
# app/services/citation_detector.py

class CitationDetector:
    def detect(self, response: str, brand: str) -> DetectionResult:
        # 最小实现：直接返回预期结果
        if brand in response:
            return DetectionResult(
                reference_type=ReferenceType.DIRECT_QUOTE,
                confidence=0.9
            )
        return DetectionResult(
            reference_type=ReferenceType.NO_REFERENCE,
            confidence=1.0
        )
```

#### REFACTOR 阶段 - 重构代码

- **前提**：所有测试通过
- **动作**：优化代码结构、消除重复、提升可读性
- **验证**：持续运行测试，确保测试始终通过
- **原则**：
  - 小步重构，每次改动后运行测试
  - 遵循 SOLID 原则和设计模式
  - 不引入新功能（新功能走新的 TDD 循环）
  - 关注代码异味（重复、过长函数、魔法数字等）

**重构后示例**：
```python
# app/services/citation_detector.py

class CitationDetector:
    def detect(self, response: str, brand: str) -> DetectionResult:
        if not response or not brand:
            raise ValueError("Response and brand are required")
        
        if self._is_direct_reference(response, brand):
            return self._create_result(ReferenceType.DIRECT_QUOTE, 0.9)
        
        if self._is_indirect_reference(response, brand):
            return self._create_result(ReferenceType.INDIRECT_REFERENCE, 0.6)
        
        return self._create_result(ReferenceType.NO_REFERENCE, 1.0)
    
    def _is_direct_reference(self, response: str, brand: str) -> bool:
        return brand in response
    
    def _create_result(self, ref_type: ReferenceType, confidence: float) -> DetectionResult:
        return DetectionResult(reference_type=ref_type, confidence=confidence)
```

## 测试层次

GEO 平台采用四层测试金字塔：

```
                    ▲
                   /│\
                  / │ \        E2E 测试（端到端）
                 /  │  \       覆盖关键用户旅程
                /───┼───\      占比：5%
               /    │    \
              /─────┼─────\    集成测试
             /      │      \   模块间交互验证
            /───────┼───────\  占比：15%
           /        │        \
          /─────────┼─────────\ 单元测试
         /          │          \ 单个函数/类验证
        /───────────┼───────────\ 占比：60%
       /            │            \
      /─────────────┼─────────────\ Agent 测试
     /              │              \ Agent 行为验证
    /───────────────┼───────────────\ 占比：20%
   ───────────────────────────────────
```

### 1. 单元测试（Unit Tests）

| 属性 | 说明 |
|------|------|
| **目标** | 验证单个函数、类或方法的行为 |
| **范围** | 隔离测试，Mock 所有外部依赖 |
| **速度** | 毫秒级，可频繁运行 |
| **工具** | Python: `pytest` + `unittest.mock`；TypeScript: `Jest` |
| **位置** | `tests/unit/` 或与被测代码同目录 `__tests__/` |
| **命名** | `test_{被测单元}_{测试场景}.py` |

**单元测试原则**：
- **FIRST 原则**：Fast（快速）、Independent（独立）、Repeatable（可重复）、Self-validating（自验证）、Timely（及时）
- **AAA 模式**：Arrange（准备）、Act（执行）、Assert（断言）
- **一个断言原则**：每个测试验证一个概念
- **Mock 外部依赖**：数据库、HTTP 请求、文件系统全部 Mock

**示例**：
```python
# tests/unit/test_citation_service.py

class TestCitationService:
    @pytest.fixture
    def mock_repo(self):
        return Mock(spec=CitationRepository)
    
    @pytest.fixture
    def service(self, mock_repo):
        return CitationService(repository=mock_repo)
    
    def test_should_save_citation_with_confidence(self, service, mock_repo):
        # Arrange
        citation = CitationCreate(brand="TestBrand", confidence=0.85)
        mock_repo.save.return_value = Citation(id=1, **citation.dict())
        
        # Act
        result = service.create_citation(citation)
        
        # Assert
        assert result.id == 1
        assert result.confidence == 0.85
        mock_repo.save.assert_called_once()
```

### 2. 集成测试（Integration Tests）

| 属性 | 说明 |
|------|------|
| **目标** | 验证多个模块之间的交互是否正确 |
| **范围** | 数据库、缓存、消息队列等真实组件 |
| **速度** | 秒级，运行频率低于单元测试 |
| **工具** | Python: `pytest` + `TestClient(FastAPI)` + `testcontainers` |
| **位置** | `tests/integration/` |
| **命名** | `test_{模块A}_{模块B}_{交互场景}.py` |

**集成测试原则**：
- 使用真实数据库（测试专用实例或 Docker 容器）
- 每个测试前后清理数据
- 测试事务隔离，失败时回滚
- 不测试外部 AI 平台 API（使用 Mock Server）

**示例**：
```python
# tests/integration/test_query_citation_flow.py

class TestQueryCitationFlow:
    @pytest.fixture
    def client(self, test_db):
        app.dependency_overrides[get_db] = lambda: test_db
        return TestClient(app)
    
    def test_should_create_query_and_detect_citations(self, client):
        # 创建查询
        response = client.post("/api/queries", json={
            "brand": "TestBrand",
            "query_template": "What is the best {brand} product?"
        })
        assert response.status_code == 201
        query_id = response.json()["id"]
        
        # 查询完成后应自动创建引用检测任务
        # 验证数据库状态
        tasks = client.get(f"/api/queries/{query_id}/tasks")
        assert len(tasks.json()) > 0
```

### 3. E2E 测试（End-to-End Tests）

| 属性 | 说明 |
|------|------|
| **目标** | 模拟真实用户操作，验证完整业务流程 |
| **范围** | 前端 + 后端 + 数据库全流程 |
| **速度** | 分钟级，CI 流水线中运行 |
| **工具** | Playwright（前端）+ pytest（后端 API） |
| **位置** | `tests/e2e/` |
| **命名** | `test_{用户旅程}_{场景}.py` |

**E2E 测试场景**：
| 场景 | 说明 | 优先级 |
|------|------|--------|
| 用户注册登录 | 完整注册 → 登录 → 访问仪表盘 | P0 |
| 创建查询 | 登录 → 创建查询 → 查看执行结果 | P0 |
| 引用检测流程 | 查询执行 → 引用检测 → 查看结果 | P0 |
| 订阅升级 | 查看计划 → 升级 → 支付 → 验证权限 | P1 |
| 代理客户管理 | 登录代理账号 → 创建客户 → 执行查询 | P1 |

### 4. Agent 测试（Agent Tests）

| 属性 | 说明 |
|------|------|
| **目标** | 验证 AI Agent 的行为和输出质量 |
| **范围** | Agent 的输入处理和输出生成 |
| **速度** | 秒级到分钟级 |
| **工具** | `pytest` + 自定义断言 |
| **位置** | `tests/agent/` |
| **命名** | `test_{agent_name}_{场景}.py` |

**Agent 测试特殊要求**：
- 使用固定的测试输入，验证输出结构和质量
- 对 AI 生成内容使用语义断言（非精确匹配）
- 评估指标：准确率、召回率、F1 分数
- 定期使用回归测试集验证 Agent 性能

**示例**：
```python
# tests/agent/test_citation_detector.py

class TestCitationDetectorAgent:
    @pytest.fixture
    def detector(self):
        return CitationDetector()
    
    def test_should_detect_brand_in_response(self, detector):
        response = "ExampleBrand is a leading company in AI."
        result = detector.detect(response, "ExampleBrand")
        
        assert result.is_referenced is True
        assert result.confidence >= 0.8
    
    def test_should_not_detect_false_positive(self, detector):
        response = "BrandX is a competitor, not ExampleBrand."
        result = detector.detect(response, "ExampleBrand")
        
        assert result.is_referenced is False
```

## 开发步骤规范

每个功能模块的开发必须遵循以下步骤：

### Step 1：需求文档

- 阅读并理解需求文档（本文档体系中的 `01-requirements/`）
- 明确功能边界、输入输出、异常场景
- 与产品经理确认需求理解无误
- 输出：功能开发任务清单

### Step 2：测试用例设计

- 基于需求编写测试用例列表
- 覆盖：正常场景、边界条件、异常处理
- 使用 Given-When-Then 格式描述
- 输出：测试用例文档

```markdown
## 测试用例：引用检测功能

### TC-001: 直接引用检测
- **Given**: AI 响应包含品牌名
- **When**: 执行引用检测
- **Then**: 返回 DIRECT_QUOTE 类型，置信度 >= 0.8

### TC-002: 未引用检测
- **Given**: AI 响应不包含品牌名
- **When**: 执行引用检测
- **Then**: 返回 NO_REFERENCE 类型，置信度 = 1.0

### TC-003: 空响应处理
- **Given**: AI 响应为空字符串
- **When**: 执行引用检测
- **Then**: 抛出 ValueError
```

### Step 3：编写失败测试

- 将测试用例转化为代码
- 运行测试，确认失败（RED）
- 提交代码（commit message: `test: add failing tests for citation detection`）

### Step 4：实现功能

- 编写最少代码使测试通过（GREEN）
- 不追求完美，先让测试通过
- 运行测试，确认通过
- 提交代码（commit message: `feat: implement citation detection`）

### Step 5：重构优化

- 审视代码，消除重复和坏味道
- 应用设计模式，提升可维护性
- 持续运行测试，确保始终通过
- 提交代码（commit message: `refactor: improve citation detection readability`）

### Step 6：集成测试

- 编写模块间的集成测试
- 验证模块协作是否正确
- 修复集成中发现的问题
- 提交代码（commit message: `test: add integration tests for query-citation flow`）

### Step 7：更新文档

- 更新模块指南文档
- 更新 API 文档（如果有接口变更）
- 更新 CHANGELOG
- 提交代码（commit message: `docs: update citation module documentation`）

## 测试覆盖要求

| 层级 | 覆盖率目标 | 检查方式 |
|------|------------|----------|
| 单元测试 | >= 80% | `pytest --cov` |
| 集成测试 | 覆盖所有 API 端点 | 手动检查 |
| E2E 测试 | 覆盖所有 P0 用户旅程 | 手动检查 |
| Agent 测试 | 覆盖核心场景 | 手动检查 |

### 覆盖率排除项

以下代码不计入覆盖率统计：
- 第三方库封装代码
- 纯数据定义（DTO、常量）
- 自动生成的代码（ORM 模型、迁移脚本）
- 调试和日志代码

## 测试运行规范

### 本地开发

```bash
# 运行所有单元测试
pytest tests/unit -v

# 运行特定模块测试
pytest tests/unit/test_citation_service.py -v

# 运行集成测试（需要本地数据库）
pytest tests/integration -v

# 运行全部测试
pytest -v

# 生成覆盖率报告
pytest --cov=app --cov-report=html

# 运行特定标记的测试
pytest -m "slow" -v
```

### CI/CD 流水线

```yaml
# .github/workflows/test.yml
jobs:
  test:
    steps:
      - name: Run Unit Tests
        run: pytest tests/unit --cov=app --cov-fail-under=80
      
      - name: Run Integration Tests
        run: pytest tests/integration
      
      - name: Run E2E Tests
        run: pytest tests/e2e
      
      - name: Upload Coverage
        uses: codecov/codecov-action@v3
```

### 测试标记（Markers）

| 标记 | 说明 | 运行方式 |
|------|------|----------|
| `unit` | 单元测试 | `pytest -m unit` |
| `integration` | 集成测试 | `pytest -m integration` |
| `e2e` | E2E 测试 | `pytest -m e2e` |
| `agent` | Agent 测试 | `pytest -m agent` |
| `slow` | 耗时测试 | `pytest -m "not slow"`（默认跳过） |
| `flaky` | 不稳定测试 | `pytest -m "not flaky"`（默认跳过） |

## 测试数据管理

### Fixtures

- 使用 `pytest.fixture` 管理测试数据
- 通用 fixtures 放在 `tests/conftest.py`
- 模块级 fixtures 放在测试文件内或同级 `conftest.py`

```python
# tests/conftest.py

@pytest.fixture(scope="function")
def test_db():
    """每个测试函数创建一个独立的数据库会话"""
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.rollback()
        db.close()

@pytest.fixture(scope="session")
def test_user():
    """测试用户数据"""
    return {
        "email": "test@example.com",
        "password": "TestPassword123!",
        "name": "Test User"
    }
```

### 工厂模式

复杂对象使用工厂函数创建：

```python
# tests/factories.py

class QueryFactory:
    @staticmethod
    def create(brand="TestBrand", **kwargs):
        defaults = {
            "brand": brand,
            "query_template": f"What is the best {brand} product?",
            "platforms": ["chatgpt", "kimi"]
        }
        defaults.update(kwargs)
        return Query(**defaults)
```

## 前端测试规范

### 组件测试（Jest + React Testing Library）

```typescript
// components/__tests__/CitationCard.test.tsx

import { render, screen } from '@testing-library/react';
import { CitationCard } from '../CitationCard';

describe('CitationCard', () => {
  it('should display citation type and confidence', () => {
    // Arrange
    const citation = {
      type: 'direct_quote',
      confidence: 0.9,
      snippet: 'ExampleBrand is...'
    };
    
    // Act
    render(<CitationCard citation={citation} />);
    
    // Assert
    expect(screen.getByText('直接引用')).toBeInTheDocument();
    expect(screen.getByText('90%')).toBeInTheDocument();
  });
});
```

### E2E 测试（Playwright）

```typescript
// tests/e2e/query-flow.spec.ts

import { test, expect } from '@playwright/test';

test('user can create and execute a query', async ({ page }) => {
  // 登录
  await page.goto('/login');
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'password');
  await page.click('button[type="submit"]');
  
  // 创建查询
  await page.click('text=新建查询');
  await page.fill('[name="brand"]', 'TestBrand');
  await page.click('text=执行查询');
  
  // 验证结果
  await expect(page.locator('text=查询执行中')).toBeVisible();
  await expect(page.locator('text=检测完成')).toBeVisible({ timeout: 30000 });
});
```

## 反模式与禁忌

### 禁止事项

| 反模式 | 说明 | 后果 |
|--------|------|------|
| 不写测试直接实现 | 开发完成后再补测试 | 测试成为验证工具而非设计工具 |
| 测试依赖外部服务 | 单元测试调用真实 API | 测试不稳定、速度慢 |
| 测试间共享状态 | 测试修改全局状态 | 测试顺序依赖，偶发失败 |
| 过度 Mock | Mock 过多导致测试无意义 | 无法发现真实集成问题 |
| 测试代码重复 | 大量复制粘贴测试代码 | 维护困难，变更成本高 |
| 忽略失败测试 | 跳过或注释失败测试 | 测试套件失去保护作用 |
| 测试实现细节 | 测试私有方法和内部状态 | 重构困难，脆弱测试 |

### 测试异味（Test Smells）

| 异味 | 症状 | 解决方式 |
|------|------|----------|
| 神秘访客 | 测试使用未定义的魔法值 | 使用命名常量或工厂 |
| 断言轮盘 | 大量断言难以定位失败 | 拆分为独立测试 |
| 重复代码 | 多个测试有相同准备代码 | 提取为 fixture |
| 沉睡的蜗牛 | 使用 sleep 等待异步 | 使用显式等待或回调 |
| 过多前置条件 | 测试准备代码超过 10 行 | 提取辅助方法 |

---

*本文档定义 GEO 平台的 TDD 开发流程，所有开发人员必须严格遵守。*
