deerflow2/.planning/codebase/TESTING.md

207 lines
7.4 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.

# 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`E2EBackend 未检测到独立 `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*