7.4 KiB
7.4 KiB
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:
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:
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:
# 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):
...
// 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 构建、页面打开、发送消息、线程前置校验。
- Backend 使用
- Teardown pattern:
- Backend 通过
tmp_path+monkeypatch隔离文件系统与全局单例(backend/tests/test_client_e2e.py)。 - Frontend Node 测试手动还原
globalThis.fetch/console.warn。
- Backend 通过
- Assertion pattern:
- Backend 侧重行为与事件序列断言(含异常、边界、并发)。
- E2E 使用
expect.poll与语义选择器(getByTestId、getByRole)减少时序抖动。
Mocking
Framework: unittest.mock + pytest.monkeypatch(Backend);函数级覆写全局对象(Frontend Node tests)
Patterns:
# 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)
// 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:
# backend/tests/test_client.py
@pytest.fixture
def mock_app_config():
model = MagicMock()
model.name = "test-model"
...
return config
// 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:
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:
@pytest.mark.anyio
async def test_heartbeat(bridge: MemoryStreamBridge):
await asyncio.wait_for(consumer(), timeout=2.0)
await expect
.poll(async () => await page.locator(".is-user, .is-assistant").count(), { timeout: 30_000 })
.toBeGreaterThan(0);
Error Testing:
with pytest.raises(ValueError, match="Invalid agent name"):
DeerFlowClient(agent_name="invalid name with spaces!")
globalThis.fetch = async () => new Response("missing", { status: 404, statusText: "Not Found" });
const converted = await promptInputFilePartToFile(...);
assert.equal(converted, null);
分层执行与覆盖风险(可执行建议)
- 当前可稳定落地的分层执行顺序:
cd backend && make lint && make testcd frontend && pnpm format && pnpm lint && pnpm typecheck && pnpm buildcd frontend && pnpm test:e2e(在具备线程测试数据的环境)
- 关键风险:
- Frontend 单元测试未接入统一脚本与 CI(
package.json无test脚本,CI 未跑test:e2e)。 - 覆盖率无门槛,变更后“测试数量增长”不等于“关键路径被覆盖”。
- E2E 对外部线程数据依赖较强,易出现“跳过即通过”。
- Frontend 单元测试未接入统一脚本与 CI(
- 建议后续规划优先级:
- 为 Frontend 增加统一单测命令并纳入 CI。
- 在 CI 增加最小 E2E smoke(可用固定 seed 数据或 mock 后端)。
- 引入覆盖率报告(先观测,再设阈值)。
简短结论
- 仓库已具备较完整 Backend 测试金字塔与前端场景化 E2E 基础,但“前端自动化测试接入 CI 不完整、覆盖率无硬约束”是当前质量收敛的主要短板。
- 后续规划应优先补齐前端测试执行入口和 CI 集成,再推进覆盖率治理,才能让测试从“存在”转为“持续有效”。
Testing analysis: 2026-04-07