# Testing Patterns

**Analysis Date:** 2026-04-07

## Test Framework

**Runner:**
- Backend: `pytest` (>=8.0.0, 定义于 `backend/pyproject.toml`)
- Frontend E2E: `@playwright/test` (定义于 `frontend/package.json`)
- Frontend 轻量单测: Node 内置 `node:test` + `node:assert/strict`（示例见 `frontend/src/core/api/stream-mode.test.ts`）
- Config: `frontend/playwright.config.ts`（E2E）；Backend 未检测到独立 `pytest.ini`/`tox.ini`

**Assertion Library:**
- Backend: `pytest` 原生断言
- Frontend E2E: Playwright `expect`
- Frontend 轻量单测: `node:assert/strict`

**Run Commands:**
```bash
cd backend && make test                         # 运行后端测试（pytest tests/ -v）
cd backend && make lint                         # 后端静态检查（ruff check + format --check）
cd frontend && pnpm test:e2e                    # 运行 Playwright E2E
cd frontend && pnpm test:e2e:ui                 # Playwright UI 模式
cd frontend && pnpm lint && pnpm typecheck      # 前端质量门禁
```

## Test File Organization

**Location:**
- Backend 测试集中在 `backend/tests/`，按模块与能力拆分（如 `test_client.py`、`test_stream_bridge.py`）。
- Frontend E2E 在 `frontend/tests/e2e/`，辅助函数在 `frontend/tests/e2e/support/`。
- Frontend 轻量模块测试与实现同目录共置（如 `frontend/src/core/uploads/*.test.mjs`）。

**Naming:**
- Python: `test_*.py`
- Playwright: `*.spec.ts`
- Node 内置测试: `*.test.ts` / `*.test.mjs`

**Structure:**
```text
backend/tests/test_*.py
frontend/tests/e2e/*.spec.ts
frontend/tests/e2e/support/*.ts
frontend/src/**/**.test.ts
frontend/src/**/**.test.mjs
```

## Test Structure

**Suite Organization:**
```python
# backend/tests/test_stream_bridge.py
@pytest.fixture
def bridge() -> MemoryStreamBridge:
    return MemoryStreamBridge(queue_maxsize=256)

@pytest.mark.anyio
async def test_publish_subscribe(bridge: MemoryStreamBridge):
    ...
```

```typescript
// frontend/tests/e2e/input-and-compose.spec.ts
test.describe("聊天工作台 / 输入区与发送", () => {
  test("DF-INPUT-001 ...", async ({ page }, testInfo) => {
    ...
  });
});
```

**Patterns:**
- Setup pattern:
  - Backend 使用 `conftest.py` 全局 fixture 与 import/mock 预处理（`backend/tests/conftest.py`）。
  - E2E 使用 `chat-helpers.ts` 封装 URL 构建、页面打开、发送消息、线程前置校验。
- Teardown pattern:
  - Backend 通过 `tmp_path` + `monkeypatch` 隔离文件系统与全局单例（`backend/tests/test_client_e2e.py`）。
  - Frontend Node 测试手动还原 `globalThis.fetch`/`console.warn`。
- Assertion pattern:
  - Backend 侧重行为与事件序列断言（含异常、边界、并发）。
  - E2E 使用 `expect.poll` 与语义选择器（`getByTestId`、`getByRole`）减少时序抖动。

## Mocking

**Framework:** `unittest.mock` + `pytest.monkeypatch`（Backend）；函数级覆写全局对象（Frontend Node tests）

**Patterns:**
```python
# backend/tests/test_model_factory.py
def _patch_factory(monkeypatch, app_config, model_class=FakeChatModel):
    monkeypatch.setattr(factory_module, "get_app_config", lambda: app_config)
    monkeypatch.setattr(factory_module, "resolve_class", lambda path, base: model_class)
```

```typescript
// frontend/src/core/uploads/prompt-input-files.test.mjs
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => { throw new Error("network down"); };
...
globalThis.fetch = originalFetch;
```

**What to Mock:**
- 外部依赖与昂贵路径：LLM、网络请求、全局单例、文件系统路径。
- 与测试目标无关的中间件/副作用（如标题生成、内存队列）应在测试中关闭。

**What NOT to Mock:**
- 核心业务状态流与事件序列（例如 `MemoryStreamBridge` 的 publish/subscribe/end 行为）。
- E2E 页面路由与关键 UI 交互链路（应尽量走真实页面流程）。

## Fixtures and Factories

**Test Data:**
```python
# backend/tests/test_client.py
@pytest.fixture
def mock_app_config():
    model = MagicMock()
    model.name = "test-model"
    ...
    return config
```

```typescript
// frontend/tests/e2e/support/chat-helpers.ts
export function newChatEntry(threadId: string) { ... }
export async function openChat(page: Page, url: string, options?: { expectInput?: boolean }) { ... }
```

**Location:**
- Backend 全局 fixture: `backend/tests/conftest.py`
- Backend 模块级 fixture/factory: 各 `backend/tests/test_*.py`
- Frontend E2E fixture/helper: `frontend/tests/e2e/support/chat-helpers.ts`

## Coverage

**Requirements:** 未检测到覆盖率阈值与强制 coverage 工具（无 `coverage.py`/`c8`/`nyc` 配置；CI 未执行 coverage 报告）。

**View Coverage:**
```bash
Not applicable（仓库未提供标准覆盖率命令）
```

## Test Types

**Unit Tests:**
- Backend: 大量纯单测，重度使用 mock 与 monkeypatch，覆盖配置解析、middleware、tools、runtime 细节。
- Frontend: 少量模块单测，采用 Node 内置测试框架，主要覆盖上传与 stream mode 边界。

**Integration Tests:**
- Backend: `test_client_e2e.py` 定位为“中层集成”，走真实模块链路并通过环境控制是否触发真实 LLM。
- Backend: 存在 `*_live.py` 用于更高成本的真实依赖验证（如 `backend/tests/test_client_live.py`、`backend/tests/test_create_deerflow_agent_live.py`）。

**E2E Tests:**
- Frontend: Playwright 场景化用例，覆盖路由、输入、历史、artifact 面板等用户路径（`frontend/tests/e2e/*.spec.ts`）。
- 执行依赖线程相关环境变量（`FRONTEND_E2E_THREAD_ID` 等），缺失时通过 `testInfo.skip` 跳过。

## Common Patterns

**Async Testing:**
```python
@pytest.mark.anyio
async def test_heartbeat(bridge: MemoryStreamBridge):
    await asyncio.wait_for(consumer(), timeout=2.0)
```

```typescript
await expect
  .poll(async () => await page.locator(".is-user, .is-assistant").count(), { timeout: 30_000 })
  .toBeGreaterThan(0);
```

**Error Testing:**
```python
with pytest.raises(ValueError, match="Invalid agent name"):
    DeerFlowClient(agent_name="invalid name with spaces!")
```

```typescript
globalThis.fetch = async () => new Response("missing", { status: 404, statusText: "Not Found" });
const converted = await promptInputFilePartToFile(...);
assert.equal(converted, null);
```

## 分层执行与覆盖风险（可执行建议）

- 当前可稳定落地的分层执行顺序：
  1. `cd backend && make lint && make test`
  2. `cd frontend && pnpm format && pnpm lint && pnpm typecheck && pnpm build`
  3. `cd frontend && pnpm test:e2e`（在具备线程测试数据的环境）
- 关键风险：
  - Frontend 单元测试未接入统一脚本与 CI（`package.json` 无 `test` 脚本，CI 未跑 `test:e2e`）。
  - 覆盖率无门槛，变更后“测试数量增长”不等于“关键路径被覆盖”。
  - E2E 对外部线程数据依赖较强，易出现“跳过即通过”。
- 建议后续规划优先级：
  1. 为 Frontend 增加统一单测命令并纳入 CI。
  2. 在 CI 增加最小 E2E smoke（可用固定 seed 数据或 mock 后端）。
  3. 引入覆盖率报告（先观测，再设阈值）。

## 简短结论

- 仓库已具备较完整 Backend 测试金字塔与前端场景化 E2E 基础，但“前端自动化测试接入 CI 不完整、覆盖率无硬约束”是当前质量收敛的主要短板。
- 后续规划应优先补齐前端测试执行入口和 CI 集成，再推进覆盖率治理，才能让测试从“存在”转为“持续有效”。

---

*Testing analysis: 2026-04-07*
