deerflow2/.planning/codebase/TESTING.md

7.4 KiB
Raw Blame History

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.tsE2EBackend 未检测到独立 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.pytest_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 构建、页面打开、发送消息、线程前置校验。
  • Teardown pattern:
    • Backend 通过 tmp_path + monkeypatch 隔离文件系统与全局单例(backend/tests/test_client_e2e.py)。
    • Frontend Node 测试手动还原 globalThis.fetch/console.warn
  • Assertion pattern:
    • Backend 侧重行为与事件序列断言(含异常、边界、并发)。
    • E2E 使用 expect.poll 与语义选择器(getByTestIdgetByRole)减少时序抖动。

Mocking

Framework: unittest.mock + pytest.monkeypatchBackend函数级覆写全局对象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.pybackend/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);

分层执行与覆盖风险(可执行建议)

  • 当前可稳定落地的分层执行顺序:
    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 单元测试未接入统一脚本与 CIpackage.jsontest 脚本CI 未跑 test:e2e)。
    • 覆盖率无门槛,变更后“测试数量增长”不等于“关键路径被覆盖”。
    • E2E 对外部线程数据依赖较强,易出现“跳过即通过”。
  • 建议后续规划优先级:
    1. 为 Frontend 增加统一单测命令并纳入 CI。
    2. 在 CI 增加最小 E2E smoke可用固定 seed 数据或 mock 后端)。
    3. 引入覆盖率报告(先观测,再设阈值)。

简短结论

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

Testing analysis: 2026-04-07