资讯详情

资讯详情

ZeroClaw 五级测试体系实战指南:从单元到 Live 的分层测试策略与共享测试基础设施

ZeroClaw 五级测试体系实战指南从单元到 Live 的分层测试策略与共享测试基础设施【免费下载链接】zeroclawFast, small, and fully autonomous AI personal assistant infrastructure, any OS, any platform — deploy anywhere, swap anything 项目地址: https://gitcode.com/gh_mirrors/ze/zeroclawZeroClaw 在tests/目录中落地了一套以文件系统布局为载体的五级测试分类法Unit / Component / Integration / System / Live每一级拥有不同的测试边界与运行成本。本指南以官方贡献者文档 testing.md 为核心骨架结合仓库中真实的测试入口、共享 mock 实现与 CI 脚本完整讲解各测试级别的定位、运行命令、级别选择原则、共享测试基础设施、JSON trace fixture 规范以及 Live 测试与手动测试的约定帮助你在为 ZeroClaw 贡献代码时用“刚好够用”的最低成本证明用户可观察的行为。测试哲学边界决定级别成本决定取舍ZeroClaw 的测试体系遵循一条核心原则每一级测试有不同的边界也有不同的成本请选择能证明你所需结论的最低级别。这里的“边界”指的是测试运行时哪些部分是真的、哪些部分被 mock 掉——边界越靠外环境越真实成本也越高。当 PR 声明的行为是用户会直接运行、点击、发送、安装或观察到的行为时还应参考 用户边界证明指南它给出了“用最小可信证据抵达被改动的最外层生产边界”的判定方法并提供了覆盖 CLI、daemon 传输、Gateway/API、渠道、Provider 路由、工具审批、后台任务、安装包等九类表面的证明矩阵。五级测试分类法ZeroClaw 的五级分类由测试文件所在目录直接决定整个体系由仓库根目录的Cargo.toml与 tests/test_component.rs、tests/test_integration.rs、tests/test_system.rs、tests/test_live.rs 四个测试入口test_*.rs串联起来每个入口以mod xxx; mod support;的方式挂载对应目录下的测试模块与共享基础设施。级别测试什么边界存放位置Unit单元单个函数或结构体一切皆 mocksrc/**内的#[cfg(test)]块或同目录下的tests.rsComponent组件自身边界内的单个子系统子系统真实其余全部 mocktests/component/Integration集成多个内部组件装配在一起内部实现真实外部 API mocktests/integration/System系统跨全部内部边界的完整请求 → 响应仅外部 API 被 mocktests/system/Live线上带真实外部服务的全栈什么都不 mock默认#[ignore]tests/live/从仓库实际布局看tests/component/ 已覆盖配置、Gateway、Provider、安全、插件等 20 个子系统用例如 provider_resolution.rs 对 30 个模型 Provider 工厂解析的断言tests/integration/ 聚焦 Agent 行为、渠道路由、记忆连续性等跨组件场景tests/system/ 包含首次运行初始化、多 Agent 端到端、全栈等用例tests/live/ 则集中了依赖真实凭证的 Provider 线上验证。两个非测试目录除五级测试外还有两个不参与cargo test直接收集的特殊目录目录用途tests/manual/人工驱动的测试脚本shell、Python直接运行不通过 cargotests/support/共享 mock 基础设施本身不是测试二进制由每个测试级别以mod support;方式引入运行测试命令速查ZeroClaw 为每个测试级别提供了独立的运行入口全部命令如下这些命令来自 testing.md并经过 dev/ci.sh 的 CI 包装验证cargo test # unit component integration system cargo test --lib # unit only cargo test --test component # component only cargo test --test integration # integration only cargo test --test system # system only cargo test --test live -- --ignored # live (requires API credentials) cargo test --test integration agent # filter within a level cargo nextest run --locked --workspace --exclude zeroclaw-desktop # what CI runs ./scripts/ci/parallel_runtime_test_gate.sh # repeated same-process runtime/channel tests ./dev/ci.sh all # full CI battery (Docker) ./dev/ci.sh firmware-protocol # standalone firmware protocol host gate (Docker) ./dev/ci.sh test-component # level-specific CI commands (Docker)要点说明入口映射--test component/--test integration/--test system/--test live分别对应 tests/test_component.rs 等四个入口文件Cargo 会将每个test_*.rs编译成独立的测试二进制。级别内过滤直接追加子串如cargo test --test integration agent只运行名称中含agent的用例。CI 运行器必需 CI 使用cargo nextest run --locked --workspace --exclude zeroclaw-desktop。如 dev/ci.sh 注释所说明nextest 与本地 Docker 路径的cargo test选择了相同的 workspace 包边界但运行器、调度、隔离与报告行为不同——nextest 让每个测试二进制在独立进程中运行并输出按二进制划分的 JUnit 报告cargo test则使用测试框架默认的进程模型。Docker 包装dev/ci.sh 提供了test-component、test-integration、test-system、test-live、test-manual等逐级别命令均通过run_in_ci在 CI 容器内执行对应 cargo 命令all命令则依次跑 lint、firmware-protocol、test、build、security、docker-smoke。firmware-protocol 门禁firmware-protocol命令检查独立于根 Cargo workspace 之外的 firmware/zeroclaw-fw-protocol crate。由于该 crate 位于根 workspace 之外常规cargo test --workspace不会覆盖到它因此 scripts/ci/firmware_protocol_gate.sh 成为其格式化formatting、严格 Clippy 与锁定依赖测试locked-test检查的权威定义——必需 CI 与 pre-push 钩子调用的是同一个脚本保证本地与 CI 行为完全一致。并行运行时门禁parallel runtime gate./scripts/ci/parallel_runtime_test_gate.sh该脚本scripts/ci/parallel_runtime_test_gate.sh以 16 个 harness 线程重复运行完整的zeroclaw-runtime与zeroclaw-channels库测试二进制。运行整个二进制而非过滤后的子集是刻意为之它能检测状态变更测试与原本无关的 Agent 回合之间的相互干扰——这类问题只有让全部测试在同进程内并发共跑才会暴露而过滤后的测试永远无法发现。脚本的三个环境变量均带默认值与正整数校验环境变量默认值作用ZEROCLAW_PARALLEL_TEST_RUNS3每个 crate 的重复运行次数ZEROCLAW_PARALLEL_TEST_THREADS16单次运行的 harness 线程数ZEROCLAW_PARALLEL_TEST_SCOPEall作用域allruntime channels或channels仅 channels触发策略凡改动zeroclaw-runtime、zeroclaw-channels、workspace 依赖清单或门禁自身 CI 文件的 PR必需 CI 会在独立 job 中运行该门禁其他 PR 跳过推送到master与 merge queue 的运行则始终保留这一完整的回归兜底。如何为新测试选择级别官方文档给出了一张“决策表”按测试意图对号入座只想隔离测试一个子系统→tests/component/要测多个组件装配在一起→tests/integration/要测端到端完整消息流→tests/system/需要真实 API 密钥→tests/live/并加上#[ignore]创建测试文件后还有两个强制动作把新模块登记到所在级别的mod.rs例如 tests/component/mod.rs其中还大量使用#[cfg(feature ...)]按 feature 条件编译模块复用 tests/support/ 中的共享基础设施。共享测试基础设施每个测试二进制都会mod support;从而可以通过crate::support::*使用共享 mock。这一定义位于 tests/support/mod.rs它对外公开了MockModelProvider、RecordingModelProvider、EchoTool、CountingTool、FailingTool、RecordingTool等类型。各模块职责如下模块内容mock_model_provider.rsMockModelProviderFIFO 脚本化响应、RecordingModelProvider响应 捕获每次请求、TraceLlmModelProviderJSON fixture 回放mock_tools.rsEchoTool、CountingTool、FailingTool、RecordingToolmock_channel.rsTestChannel捕获发送内容、记录 typing 事件helpers.rsmake_memory()、make_observer()、build_agent()、text_response()、tool_response()、StaticRecallMemory等trace.rsLlmTrace、TraceTurn、TraceStep类型 LlmTrace::from_file()全部从zeroclaw-evalcrate 再导出assertions.rsverify_expects()用于声明式 trace 断言典型用法与文档一致的代码示例use crate::support::{MockModelProvider, EchoTool, CountingTool}; use crate::support::helpers::{build_agent, text_response, tool_response};Mock Provider 家族从 tests/support/mock_model_provider.rs 的源码可以看清三者差异MockModelProvider内部持有一个MutexVecChatResponse每次chat()调用按 FIFO 顺序remove(0)弹出一条预置响应响应耗尽后返回兜底文本fallback/done不会让测试因越界而崩溃。RecordingModelProvider在弹响应的同时把每次请求的完整消息历史request.messages.to_vec()压入ArcMutexVecVecChatMessage便于断言 Agent 实际发给模型的上下文序列。构造函数返回(provider, recorded)二元组测试持有 recorded 句柄做检查。TraceLlmModelProvider从LlmTracefixture 构造把各 turn 的 step 摊平成响应队列与前面两者不同队列耗尽时会直接anyhow::bail!报错TraceLlmModelProvider(...) exhausted: no more steps in trace——这正是 trace 机制“Agent 调用 Provider 次数超过 step 数即测试失败”的源码实现。三者都实现了ModelProvidertrait 与Attributabletrait用于归因/审计体系并区分TraceResponse::Text纯文本与TraceResponse::ToolCalls工具调用将参数序列化为 JSON 字符串两种回放形态。Mock Tool 家族tests/support/mock_tools.rs 提供了四个具有明确测试语义的工具工具行为EchoTool回显输入参数message默认(empty)CountingTool每次执行计数器 1输出call #N用于验证调度次数FailingTool永远返回success: false与错误Service unavailable: connection timeout模拟外部服务故障RecordingTool把每次调用的参数全部压入调用记录供事后断言构建辅助函数tests/support/helpers.rs 封装了 Agent 装配细节make_memory()构造backend: none的内存记忆后端build_agent()/build_agent_xml()分别使用NativeToolDispatcher与XmlToolDispatcher构建测试 Agentbuild_agent_with_sqlite_memory()则装配真实 SQLite 记忆后端text_response()/tool_response()是构造ChatResponse的便捷函数StaticRecallMemory是一个固定返回预设条目的记忆桩。JSON Trace Fixtures声明式对话脚本Trace fixture 是存放在 tests/fixtures/traces/ 下的 JSON 文件用于替代内联的 mock 设置以声明式对话脚本的形式描述 Agent 应该收到哪些 LLM 响应——相比mockall链式设置它们易读得多、也易改得多。仓库现有multi_tool_chain.json、single_tool_echo.json、smoke_greeting.json三个样例。工作流程TraceLlmModelProvider加载 fixture 并实现ModelProvidertrait每次provider.chat()调用按 FIFO 顺序返回 fixture 中的下一个 step真实工具正常执行例如EchoTool会真正处理自己的参数所有 turn 结束后verify_expects()检查声明式断言如果 Agent 调用 Provider 的次数超过 step 数测试失败。Fixture 格式{ model_name: test-name, turns: [ { user_input: User message, steps: [ { response: { type: text, content: LLM response, input_tokens: 20, output_tokens: 10 } } ] } ], expects: { response_contains: [expected text], tools_used: [echo], max_tool_calls: 1 } }响应类型response.type有两种text纯文本回复与tool_callsLLM 请求执行工具需附带tool_calls数组每项含id、name、arguments。input_tokens/output_tokens为可选字段缺省时为 0。expects字段在文档列出的基础上crates/zeroclaw-eval/src/case.rs 的TraceExpects结构体给出了完整清单字段语义response_contains最终响应必须包含的子串列表response_not_contains最终响应必须不包含的子串列表tools_used必须被调用过的工具名列表tools_not_used必须未被调用的工具名列表max_tool_calls工具调用次数上限all_tools_succeeded是否要求每次工具调用都成功response_matches最终响应必须匹配的正则表达式列表min_tool_calls工具调用次数下限源码补充exact_tool_calls精确的工具调用次数源码补充tool_arguments_contain派发给某工具的参数载荷必须包含的子串可配合call_index断言第 N 次调用源码补充tool_results_contain某工具返回结果载荷必须包含的子串源码补充源码级的严格校验防“静默空转”设计trace 机制最值得借鉴的设计在于它的反脆弱校验。LlmTrace与TraceExpects都标注了#[serde(deny_unknown_fields)]意味着拼错的键例如把response_contains写成response_contain会在加载阶段直接报错而不是被静默丢弃后让一条“啥也没断言”的用例蒙混过关。LlmTrace::from_file()见 crates/zeroclaw-eval/src/case.rs还会拒绝以下五类有问题的 fixture无有效断言expects.is_empty()零条断言会在没有任何证据的情况下虚空通过vacuous pass不能用于认证必需的回归门禁零长度条目如response_contains: []——空串对每个响应都成立等于没断言空 payload 字段tool_arguments_contain/tool_results_contain中的tool或needle为空无效的工具调用边界如min_tool_calls: 0、min max、exact超出[min, max]区间零个对话 turnturns: []意味着 Agent 一次都没被驱动却可能对max_tool_calls: 0之类看似真实的断言“判绿”。配套的 assertions.rs 中的verify_expects()实现了全部正向/负向断言包括对response_matches的正则编译失败会直接 panic 并带出失败标签。这类“必须 100% 通过、不允许静默缩水”的回归门禁设计完整体现于 crates/zeroclaw-eval/src/case.rs 内建的 20 个单元测试中。Live 测试约定Live 测试会命中真实外部服务并产生真实费用因此默认全部#[ignore]只有显式选择才会运行。约定如下来自 testing.md永远加#[ignore]绝不允许在普通cargo test下运行凭据从env::var(ZEROCLAW_TEST_*)读取不读取操作员的配置文件——Live 测试必须保持封闭hermetic用cargo test --test live -- --ignored --nocapture运行。源码印证以 tests/live/providers.rs 为例其模块头注释明确“All tests in this module require real external API credentials and are marked with#[ignore]”每个用例都带#[ignore requires live OpenAI Codex OAuth credentials]之类的说明同时该文件用RECALL_TEMPERATURE 0.0贪心采样保证多轮召回测试的确定性——因为用例断言了精确的秘密词zephyr必须出现在第二轮回复中非确定性输出会让测试不稳定。数据库测试属于集成测试文档明确了一条硬性规则不要为测试 schema 或 SQL 而 mock SQLite集成测试必须命中真实数据库。“mock 通过但生产失败”这类缺陷是真实存在的ZeroClaw 曾经踩过这个坑。这也解释了为何 helpers.rs 会专门提供build_agent_with_sqlite_memory()——在临时目录中装配真实 SQLite 记忆后端再跑测试。手动测试tests/manual/ 存放无法通过cargo test自动化、需要人工驱动的测试脚本直接运行即可。渠道特定的人工冒烟测试位于 tests/manual/ 下的渠道子目录中——仓库现有telegram/含quick_test.sh、test_telegram_integration.sh、generate_test_messages.py与测试说明testing-telegram.md与tmux/两个子目录以及test_dockerignore.sh。这些脚本由dev/ci.sh test-manual在 CI 容器内统一驱动。小结一条贯穿全仓的测试主线程从根 workspace 的四个test_*.rs入口到 tests/support/ 的共享 mock再到 tests/fixtures/traces/ 的声明式对话脚本最后落到 crates/zeroclaw-eval 的严格 fixture 校验与 scripts/ci/ 的两个独立门禁ZeroClaw 用文件系统布局把“测试边界”变成了目录结构的一部分。为贡献代码时遵循“先定用户可观察的行为 → 找出最外层受影响边界 → 选择能抵达该边界的最低级别 → 复用tests/support/与 trace fixture → 需要真实服务时升级到#[ignore]的 Live 测试”这条链路即可用最小的成本获得足够可信的回归保障。【免费下载链接】zeroclawFast, small, and fully autonomous AI personal assistant infrastructure, any OS, any platform — deploy anywhere, swap anything 项目地址: https://gitcode.com/gh_mirrors/ze/zeroclaw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →