跳到主要内容

测试框架文档

本文档介绍 Wegent 项目的单元测试框架设置。

概述

该项目为所有模块提供了全面的单元测试支持:

  • 后端 (FastAPI): pytest + pytest-asyncio + pytest-cov + pytest-mock
  • 执行器 (AI Agent 引擎): pytest + pytest-mock + pytest-asyncio
  • 执行器管理器 (任务管理): pytest + pytest-mock + pytest-cov
  • 共享模块 (工具库): pytest + pytest-cov
  • 前端 (Next.js + React 19): Jest + @testing-library/react

当前测试覆盖率

后端 (backend/)

  • ✅ 核心安全:身份验证、JWT 令牌、password 哈希
  • ✅ 配置管理
  • ✅ 异常处理
  • ✅ 用户服务和模型
  • ✅ GitHub 仓库提供者
  • ⏳ API 端点(占位符目录已存在)

执行器 (executor/)

  • ✅ Agent 工厂
  • ✅ 基础 agent 类
  • ✅ 模拟 AI 客户端交互(Anthropic、OpenAI)

执行器管理器 (executor_manager/)

  • ✅ 基础执行器类
  • ✅ 任务调度器
  • ✅ Docker 执行器和工具
  • ✅ Docker 常量和配置

共享模块 (shared/)

  • ✅ 加密工具
  • ✅ 敏感数据脱敏(令牌、API 密钥等)

前端 (frontend/)

  • ⏳ 组件测试(已建立基本设置)
  • ⏳ Hook 测试
  • ⏳ 工具函数测试

测试覆盖率目标

  • 目标:初期达到 40-60% 的代码覆盖率
  • 优先级:核心业务逻辑和关键路径
  • 策略:逐步提高覆盖率

后端测试

运行测试

cd backend
pytest # 运行所有测试
pytest tests/core/ # 仅运行核心测试
pytest --cov=app # 运行并生成覆盖率报告
pytest -v # 详细输出
pytest -k test_security # 运行特定测试模式
pytest -m unit # 仅运行单元测试
pytest -m integration # 仅运行集成测试

测试结构

backend/tests/
├── conftest.py # 全局测试 fixture(测试夹具)
├── core/ # 核心基础设施测试
│ ├── test_security.py # 身份验证和 JWT 测试
│ ├── test_config.py # 配置测试
│ └── test_exceptions.py # 异常处理器测试
├── services/ # 服务层测试
│ └── test_user_service.py # 用户服务测试
├── models/ # 数据模型测试
│ └── test_user_model.py # 用户模型测试
├── repository/ # 仓库集成测试
│ └── test_github_provider.py
└── api/ # API 端点测试(占位符)

测试配置

后端使用 pytest.ini 进行配置,包含以下设置:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--strict-markers
--cov=app
--cov-report=term-missing
--cov-report=html
--cov-report=xml
asyncio_mode = auto
markers =
unit: Unit tests
integration: Integration tests
slow: Slow running tests

关键 Fixture(测试夹具)

  • test_db:SQLite 内存数据库会话(函数作用域)
  • test_settings:带有覆盖值的测试设置
  • test_user:测试用户实例
  • test_admin_user:测试管理员用户实例
  • test_inactive_user:非活动测试用户实例
  • test_token:测试用户的有效 JWT 令牌
  • test_admin_token:管理员用户的有效 JWT 令牌
  • test_client:带有数据库覆盖的 FastAPI 测试客户端
  • mock_redis:模拟的 Redis 客户端

执行器测试

运行测试

cd executor
pytest tests/ --cov=agents

测试结构

executor/tests/
├── conftest.py # 执行器特定的 fixture
└── agents/ # Agent 测试

关键 Fixture

  • mock_anthropic_client:模拟的 Anthropic API 客户端,用于测试 Claude 模型
  • mock_openai_client:模拟的 OpenAI API 客户端,用于测试 GPT 模型
  • mock_callback_client:模拟的回调 HTTP 客户端,用于 agent 响应
  • suppress_resource_warnings:会话作用域的 fixture,用于抑制 ResourceWarning 消息
  • cleanup_logging:会话作用域的 fixture,用于清理日志处理器并防止守护线程错误

执行器管理器测试

运行测试

cd executor_manager
pytest tests/ --cov=executors

关键 Fixture

  • mock_docker_client:模拟的 Docker SDK 客户端,用于容器操作
  • mock_executor_config:模拟执行器配置,包含镜像、CPU、内存和网络设置

测试结构

executor_manager/tests/
├── conftest.py # 执行器管理器 fixture
└── executors/ # 执行器测试
├── test_base.py
├── test_dispatcher.py
├── test_docker_executor.py
├── test_docker_utils.py
└── test_docker_constants.py

共享模块测试

运行测试

cd shared
pytest tests/ --cov=utils

测试结构

shared/tests/
└── utils/
├── test_crypto.py # 加密/解密测试
└── test_sensitive_data_masker.py # 敏感数据脱敏测试

已测试的关键功能

  • 加密:敏感数据的加密和解密(Git 令牌、API 密钥)
  • 数据脱敏:日志和输出中敏感信息的自动脱敏
    • GitHub 令牌(github_pat_*
    • Anthropic API 密钥(sk-ant-api03-*
    • OpenAI API 密钥
    • 通用 API 密钥和 secrets
    • 文件路径保护(无误报)
    • URL 保护(无误报)

前端测试

运行测试

# 从仓库根目录运行:
pnpm --filter wecode-ai-assistant test # 运行所有测试
pnpm --filter wecode-ai-assistant run test:watch # 监视模式
pnpm --filter wecode-ai-assistant run test:coverage # 生成覆盖率报告

测试结构

frontend/src/__tests__/
├── utils/ # 工具函数测试
├── hooks/ # React hooks 测试
└── components/ # 组件测试

持续集成

GitHub Actions 工作流

测试与缓存工作流在以下情况下自动运行:

  • .github/workflows/test.yml 响应面向 main 的 PR 和 merge queue。
  • .github/workflows/ci-cache-warmup.yml 在相关依赖、源码或 CI 配置进入 main 后预热共享缓存。

CI 会先运行 .github/scripts/classify-ci-changes.sh,根据改动路径决定需要执行的 模块任务。模块依赖也会纳入判断,例如修改 shared/ 时会同时运行 Backend、 Executor Manager、Knowledge Engine、Shared 和 CLI 测试;修改 packages/chat-core/ 时会同时运行 Frontend 与 Wework 检查。修改测试 workflow 或分类脚本本身时会运行全部模块,确保 CI 编排变更经过完整验证。

在 PR 上添加 ci:all 标签可以跳过路径分类,强制运行全部模块测试、平台 E2E、 Wework 浏览器和桌面 E2E,以及 Wework macOS 内存门禁,适合路径分类可能遗漏 影响面的改动。三个测试 workflow 都会响应标签事件;没有 ci:all 标签时仍按 改动路径决定测试范围。ci:memory 仍然只触发 Wework macOS 内存门禁。

功能分支不在普通 push 事件中运行重复 CI;创建 PR 后由 pull_request 事件验证。 同一 PR 或 main 上有更新提交时,旧的未完成运行会被取消。test-summarylint-summary 始终存在,并验证所有被分类为必需的任务确实成功,未执行的无关模块 不会导致汇总任务误报失败。

CI 缓存所有权

大型缓存由 main 分支统一预热。PR 和 merge queue 可以恢复 main 缓存,但不会 保存新的副本:

  • Linux workspace node_modules 按 Node 版本和 pnpm-lock.yaml 建立一份共享缓存, 供 Tests、Lint、平台 E2E 和 Wework E2E 使用。
  • uv 只缓存下载和构建结果,不缓存项目 .venv。缓存 key 包含对应 uv.lock, 按 Python 3.10、3.11、3.12 各维护一份共享缓存,保存前执行 CI prune。
  • Playwright browser、Next.js build cache、Claude Code CLI 和桌面 Cargo target 采用显式 restore/save;只有 refs/heads/main 可以 save。Claude Code CLI 通过仓库内的 package-lock.json 锁定完整依赖图和包完整性。
  • Rust 单测、Windows check、发布、快照和 macOS 内存门禁通过 sccache 复用编译器 输出。main 预热会在 macos-14 上执行与内存门禁一致的桌面 --build-only,非 main 任务以只读模式访问共享 sccache。
  • Wework Desktop Core E2E 继续使用 main 拥有的 Cargo target cache,因为它需要 在多个桌面 job 之间复用同一套完整二进制产物。
  • 平台 E2E、Release 和 Snapshot 的 Docker BuildKit cache 存放在对应 GHCR 镜像的 build cache tag,不占用 GitHub Actions dependency cache 配额。
  • Executor 和 Tauri 系统依赖安装前先检查 runner 已安装的软件包;APT 下载缓存只有 main 写入,PR 只恢复。

.github/workflows/ci-cache-warmup.yml 在相关源码、依赖锁文件或缓存实现合入 main 后运行,并拒绝非 main 的手动预热。它只下载依赖或编译缓存,不重复执行 已经由 merge queue 验证过的测试。不要在 PR/merge-group 上新增大型自动保存缓存;需要增加缓存时,应采用 actions/cache/restore 和仅限 mainactions/cache/save,或使用已有共享 action。

写入 GitHub Actions 输出

写入 $GITHUB_OUTPUT 时,每一行必须是 name=value 格式。不要把会输出进度或诊断 信息的命令替换结果直接写入该文件;先只提取所需的值,再写入输出。例如,解析 Playwright 版本时:

version="$(pnpm exec playwright --version | sed -n 's/^Version //p')"
echo "version=$version" >> "$GITHUB_OUTPUT"

这样包管理器的供应链检查或其他辅助日志不会使工作流输出格式失效。

工作流任务

常规 PR 测试必须覆盖各模块测试,不能用 E2E 任务代替:

  1. test-backend:使用 Python 3.11 和 uv run pytest 运行 Backend 测试。
  2. test-executor:运行 Rust Executor 的 cargo fmt --checkcargo test --all-features 和 Clippy。该任务把 CARGO_TARGET_DIR 设置到 runner 临时目录,避免共享构建目录污染或占满工作区。
  3. test-executor-managertest-sharedtest-knowledge-engine:使用 uv run pytest 运行对应 Python 模块测试。
  4. test-frontend:运行 Chat Core 与 Frontend 单元测试。
  5. test-wework:使用 Vitest 运行 Wework 单元测试并生成覆盖率。
  6. test-wegent-clitest-wegent-cli-integration:分别运行 CLI 单元测试, 以及依赖 MySQL、Redis 和真实 Backend 的集成测试。
  7. test-summary:始终运行并依赖以上所有任务;任何模块测试失败时,汇总任务必须失败。

E2E 使用独立 workflow:.github/workflows/e2e-tests.yml 覆盖产品端到端流程, .github/workflows/wework-e2e.yml 覆盖 Wework 流程。它们提供额外的跨模块验证, 不会替代 .github/workflows/test.yml 中的模块测试。平台 E2E 只在 Backend、 Frontend、Executor、Executor Manager、Shared、Chat Shell、Chat Core、Docker 或相关构建配置变化时运行,因此 Wework-only PR 不会再启动无关的平台 E2E。 Draft PR 跳过昂贵的 E2E,转为 Ready for review 后再运行。

完整的平台 E2E 每天 UTC 02:00 定时运行;完整 Wework E2E 每天 UTC 04:00 定时 运行。定时任务与 PR、merge queue 使用不同的 concurrency group,互相不会取消。

覆盖率报告

覆盖率报告会自动上传到 Codecov(如果已配置)。

Mock(模拟)策略

外部 API

  • GitHub/GitLab/Gitee:使用 httpx-mockpytest-mock 进行模拟
  • Anthropic/OpenAI:模拟 SDK 客户端
  • Redis:使用 fakeredis 或 mock

数据库

  • 测试数据库:SQLite 内存数据库
  • 隔离:每个测试获得一个新的事务
  • 清理:每个测试后自动回滚

Docker

  • 模拟 docker.from_env() 和容器操作

最佳实践

编写测试

  1. 每个测试一个断言:每个测试应验证一个特定的行为
  2. 描述性名称:使用清晰、描述性的测试函数名称来说明正在测试什么
  3. AAA 模式:安排(Arrange)、执行(Act)、断言(Assert)- 清晰地组织测试结构
  4. 模拟外部依赖:永远不要调用真实的外部服务(API、数据库等)
  5. 使用 fixture:通过 fixture 共享常见的测试设置以减少重复
  6. 测试边界情况:包含错误条件、边界值和异常输入的测试
  7. 保持测试独立:每个测试应该能够独立运行,不依赖其他测试

安全测试最佳实践

该项目在 backend/tests/core/test_security.py 中包含了全面的安全测试示例:

  • Password 哈希和验证(bcrypt)
  • JWT 令牌创建和验证
  • 令牌过期处理
  • 使用有效/无效凭据的用户身份验证
  • 非活动用户检测
  • 基于角色的访问控制(管理员与普通用户)

安全功能的测试模式示例:

@pytest.mark.unit
class TestPasswordHashing:
"""Test password hashing and verification functions"""

def test_verify_password_with_correct_password(self):
"""Test password verification with correct password"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password(password, hashed) is True

def test_verify_password_with_incorrect_password(self):
"""Test password verification with incorrect password"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password("wrongpassword", hashed) is False

测试组织

@pytest.mark.unit
class TestFeatureName:
"""Test feature description"""

def test_success_case(self):
"""Test successful operation"""
# Arrange(安排)
data = {"key": "value"}

# Act(执行)
result = function_under_test(data)

# Assert(断言)
assert result == expected_value

def test_error_case(self):
"""Test error handling"""
with pytest.raises(ExpectedException):
function_under_test(invalid_data)

使用测试标记

测试标记有助于分类和选择性运行测试:

# 仅运行单元测试
pytest -m unit

# 仅运行集成测试
pytest -m integration

# 运行慢速测试
pytest -m slow

# 跳过慢速测试
pytest -m "not slow"

异步测试

@pytest.mark.asyncio
async def test_async_function():
"""Test asynchronous function"""
result = await async_function()
assert result is not None

后端的 pytest.ini 包含 asyncio_mode = auto,可自动检测并运行异步测试。

添加新测试

后端

  1. 在相应的 tests/ 子目录中创建测试文件(例如,tests/services/test_new_service.py
  2. conftest.py 导入必要的 fixture
  3. 使用 @pytest.mark.unit@pytest.mark.integration 对测试进行分类
  4. 遵循 AAA(安排-执行-断言)模式
  5. 编写具有描述性名称的测试类和方法
  6. 在提交前本地运行测试:pytest tests/ -v
  7. 确保覆盖率得到维持或提高:pytest --cov=app --cov-report=term-missing

前端

  1. src/__tests__/ 中创建与源代码结构匹配的测试文件
  2. 使用 @testing-library/react 进行组件测试
  3. 模拟 API 调用和外部依赖
  4. 确保测试通过:pnpm --filter wecode-ai-assistant test

调试测试

后端

# 运行特定测试并输出详细信息
pytest tests/core/test_security.py::TestPasswordHashing::test_verify_password_with_correct_password -v

# 在失败时进入调试器
pytest --pdb

# 显示 print 语句
pytest -s

前端

# 在监视模式下运行测试
pnpm --filter wecode-ai-assistant run test:watch

# 调试特定测试文件
pnpm --filter wecode-ai-assistant test -- src/__tests__/utils/test_example.test.ts

配置文件

后端

  • backend/pytest.ini:pytest 配置,包含覆盖率设置和测试标记
    • 启用详细输出、严格标记和自动异步模式
    • 配置终端、HTML 和 XML 格式的覆盖率报告
    • 定义自定义标记:unitintegrationslow

执行器/执行器管理器/共享模块

  • pytest.ini:模块特定的 pytest 配置
  • 与后端类似的设置,但具有模块特定的覆盖率目标

前端

  • frontend/jest.config.ts:Jest 配置
  • frontend/jest.setup.js:测试环境设置

未来改进

  • 将覆盖率提高到 70-80%
  • 为 API 端点添加集成测试(当前为占位符)
  • 为关键用户流程添加 E2E 测试
  • 性能/负载测试
  • 使用 mutmut 进行变异测试
  • 添加更多前端组件测试
  • 实现数据库迁移测试
  • 为 WebSocket 连接和实时功能添加测试

故障排除

常见问题

测试中的导入错误:

  • 确保您从正确的目录运行 pytest
  • 检查模块是否已安装:uv sync

数据库错误:

  • 测试使用 SQLite 内存数据库,无需设置
  • 检查 fixture 是否正确导入

前端测试失败:

  • 确保已安装 Node.js 20+
  • 在仓库根目录运行 pnpm install --frozen-lockfile 以安装确切的依赖版本
  • 清除 Jest 缓存:pnpm --filter wecode-ai-assistant exec jest --clearCache

资源