# 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*