Skip to content

测试策略

运行测试

必须使用 run_tests.sh

bash
# 完整测试套件
scripts/run_tests.sh

# 单个目录
scripts/run_tests.sh tests/gateway/

# 单个测试
scripts/run_tests.sh tests/agent/test_foo.py::test_x

# 传递 pytest 参数
scripts/run_tests.sh -v --tb=long

永远不要直接调用 pytestscripts/run_tests.sh 确保本地环境与 CI 一致:

环境变量不使用脚本使用脚本
Provider API Keys你的真实密钥全部清空
HOME / ~/.hermes/你的真实配置临时目录
时区本地时区UTC
语言本地语言C.UTF-8
xdist workers-n auto (20+核)-n 4 (匹配 CI)

如果必须直接运行 pytest

bash
source venv/bin/activate
python -m pytest tests/ -q -n 4

测试架构

conftest.py 自动隔离

tests/conftest.py 提供了 autouse fixture:

  • 重定向 HERMES_HOME 到临时目录
  • 清空所有 API key 环境变量
  • 设置 TZ=UTCLANG=C.UTF-8
  • 每个 test function 获得独立的临时目录

测试目录结构

tests/
├── conftest.py           # autouse 隔离 fixture
├── agent/                # agent/ 模块测试
├── cli/                  # CLI 测试
├── gateway/              # 网关测试
├── tools/                # 工具测试
├── hermes_cli/           # CLI 子命令测试
├── cron/                 # 定时任务测试
├── skills/               # 技能系统测试
├── run_agent/            # Agent 运行测试
├── tui_gateway/          # TUI 后端测试
├── integration/          # 集成测试(需要外部服务)
├── e2e/                  # 端到端测试
├── fakes/                # 测试替身
└── environments/         # 环境后端测试

测试标记

  • @pytest.mark.integration — 需要外部服务的测试,默认跳过
  • 无标记 — 单元测试,默认运行

不要写 Change-Detector 测试

什么是 change-detector 测试

断言预期会变化的数据的测试。这类测试没有行为覆盖价值,只会在数据更新时破坏 CI。

不要写

python
# 快照测试 — 每次模型更新都会失败
assert "gemini-2.5-pro" in _PROVIDER_MODELS["gemini"]

# 版本号断言 — 每次配置升级都会失败
assert DEFAULT_CONFIG["_config_version"] == 21

# 计数断言 — 每次添加 provider/skill 都会失败
assert len(_PROVIDER_MODELS["huggingface"]) == 8

应该写

python
# 行为测试:验证机制是否工作
assert "gemini" in _PROVIDER_MODELS
assert len(_PROVIDER_MODELS["gemini"]) >= 1

# 不变量测试:验证数据间的关系
for m in _PROVIDER_MODELS["huggingface"]:
    assert m.lower() in DEFAULT_CONTEXT_LENGTHS_LOWER

# 迁移测试:验证升级到最新版本
assert raw["_config_version"] == DEFAULT_CONFIG["_config_version"]

判断规则

  • 如果测试看起来像当前数据的快照 → 删除
  • 如果测试断言两个数据之间的关系 → 保留

Profile 测试模式

测试 Profile 功能时,需要同时 mock Path.home() 和设置 HERMES_HOME

python
@pytest.fixture
def profile_env(tmp_path, monkeypatch):
    home = tmp_path / ".hermes"
    home.mkdir()
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.setenv("HERMES_HOME", str(home))
    return home

基于 MIT 许可发布