ZeroClaw-11-开发工作流与测试深度解析 🔗
深入解析 ZeroClaw 的 Git 工作流、代码标准、测试策略,理解每一个流程设计的背后考量。
适合阅读人群:ZeroClaw 贡献者、DevOps 工程师、关注代码质量的工程师
引言:为什么需要规范的工作流? 🔗
当项目规模扩大、贡献者增多时,没有规范的代价是巨大的:
- 代码风格不一致,review 困难
- 测试覆盖率下降,bug 增多
- 协作冲突频繁,进度受阻
ZeroClaw 的工作流设计基于实际痛点,每一个规则都是为了解决具体问题。
一、Git 工作流设计 🔗
1.1 为什么使用 GitHub Flow? 🔗
flowchart LR
MAIN["main"] --> FEATURE1["feature/telegram-fix"]
MAIN --> FEATURE2["feature/new-provider"]
MAIN --> FEATURE3["doc/improve-readme"]
FEATURE1 --> PR1["Pull Request"]
FEATURE2 --> PR2["Pull Request"]
FEATURE3 --> PR3["Pull Request"]
PR1 -->|review| MERGE1["merge to main"]
PR2 -->|review| MERGE2["merge to main"]
PR3 -->|review| MERGE3["merge to main"]
MERGE1 --> MAIN
MERGE2 --> MAIN
MERGE3 --> MAIN
GitHub Flow 的核心规则:
main分支始终可部署- 新功能从
main创建分支 - 通过 Pull Request 合并回
main - 合并前必须通过 CI 检查
为什么不用 Git Flow?
| 特性 | Git Flow | GitHub Flow(ZeroClaw) |
|---|---|---|
| 分支复杂度 | 高(develop、release、hotfix) | 低(只有 main) |
| 适合项目 | 传统软件(版本发布) | 持续部署服务 |
| 学习成本 | 高 | 低 |
| 发布流程 | 复杂 | 简单(main 即发布) |
ZeroClaw 的选择:GitHub Flow
原因:
- ZeroClaw 是服务化部署,不需要复杂的版本发布流程
- 减少分支管理的认知负担
- 符合现代开源项目的实践
1.2 分支命名规范 🔗
feature/<描述> # 新功能
fix/<描述> # bug 修复
doc/<描述> # 文档更新
refactor/<描述> # 重构(无功能变化)
perf/<描述> # 性能优化
dependabot/<...> # 依赖更新(自动)
为什么要规范命名?
- 一目了然:从分支名就知道目的
- 自动分类:可以通过脚本统计各类 PR 数量
- CI 条件:某些工作流可以根据前缀触发
对比示例:
| ❌ 不好的命名 | ✅ 好的命名 |
|---|---|
fix-telegram |
fix/telegram-retry-logic |
new-feature |
feature/ollama-provider-support |
update |
doc/update-install-guide |
1.3 Worktree 工作流详解 🔗
什么是 Git Worktree?
允许你在同一仓库的多个目录中同时检出不同分支。
# 创建 worktree
git worktree add ../zeroclaw-telegram-fix feature/telegram-fix
# 现在你有两个目录
# ./zeroclaw/ - main 分支
# ./zeroclaw-telegram-fix/ - telegram-fix 分支
为什么推荐使用 worktree?
场景1:并行开发
# 你在修复一个 bug(分支 fix/memory-leak)
# 突然需要紧急处理另一个问题
# 传统方式:git stash + checkout
# 容易混乱,容易丢失 stash
# Worktree 方式
git worktree add ../zeroclaw-hotfix fix/urgent-crash
# 完全独立的工作目录,互不干扰
场景2:代码审查
# 需要审查同事的 PR
git worktree add ../zeroclaw-review pr/123
cd ../zeroclaw-review
cargo test # 在隔离环境测试
Worktree 的优势:
- 完全隔离:编译缓存、IDE 配置互不影响
- 并行编译:可以同时编译不同分支
- 无 stash 困扰:不需要临时存储修改
使用建议:
# 创建命名规范的 worktree 目录
mkdir -p ~/worktrees/zeroclaw
cd ~/worktrees/zeroclaw
# 为每个任务创建 worktree
git worktree add wt/telegram-fix feature/telegram-fix
git worktree add wt/doc-update doc/api-reference
# 完成后清理
git worktree remove wt/telegram-fix
二、提交规范 🔗
2.1 Conventional Commits 🔗
<type>(<scope>): <description>
[optional body]
[optional footer]
为什么使用规范提交?
- 自动生成 CHANGELOG
# feat 提交 → 自动包含在 Features 部分
# fix 提交 → 自动包含在 Bug Fixes 部分
# BREAKING CHANGE → 自动包含在 Breaking Changes 部分
- 版本号自动化
feat → minor bump (1.0.0 → 1.1.0)
fix → patch bump (1.0.0 → 1.0.1)
BREAKING CHANGE → major bump (1.0.0 → 2.0.0)
- 搜索和过滤
# 快速找到所有 bug 修复
git log --grep="fix:"
# 统计某范围的提交类型
git log --pretty=format:"%s" | grep -c "^feat"
2.2 提交类型详解 🔗
| 类型 | 使用场景 | 示例 |
|---|---|---|
feat |
新功能 | feat(telegram): add webhook support |
fix |
Bug 修复 | fix(memory): resolve sqlite deadlock |
doc |
文档 | doc(readme): update installation steps |
refactor |
重构 | refactor(agent): extract message handler |
perf |
性能优化 | perf(embedding): cache vector results |
test |
测试相关 | test(tools): add shell tool tests |
chore |
杂项 | chore(deps): update tokio to 1.40 |
为什么区分 refactor 和 fix?
fix:修复 bug,行为改变refactor:代码结构改变,行为不变(应该无 bug)
区分的重要性:
- 用户关心
fix(影响使用) - 开发者关心
refactor(影响维护) - 如果重构引入 bug,可以快速定位
2.3 提交信息的最佳实践 🔗
❌ 不好的提交信息:
fix bug
update
wip
test
✅ 好的提交信息:
fix(channels/telegram): handle network timeout in webhook handler
Previously, webhook handler would panic on network timeout.
Now it properly returns 503 and retries with exponential backoff.
Closes #123
提交信息应该回答:
- 做了什么(What)
- 为什么做(Why)
- 有什么影响(Impact)
三、代码审查(Code Review) 🔗
3.1 风险分层策略 🔗
flowchart TB
subgraph 低风险["🟢 低风险(自动合并)"]
L1["文档更新"]
L2["注释补充"]
L3["格式化修复"]
end
subgraph 中风险["🟡 中风险(1 人审查)"]
M1["功能增强"]
M2["新工具/渠道"]
M3["配置变更"]
end
subgraph 高风险["🔴 高风险(2 人审查+安全审查)"]
H1["安全相关(security/)"]
H2["运行时变更(runtime/)"]
H3["网关变更(gateway/)"]
H4["工具执行(tools/)"]
H5["CI/CD 变更"]
end
L1 -->|无需审查| AUTO["自动通过"]
M1 -->|1 人审查| MERGE["合并"]
H1 -->|2 人审查| SECURITY["安全审查"]
SECURITY --> MERGE
为什么需要风险分层?
资源有限原则:
- 核心维护者的时间有限
- 高风险变更需要更多审查时间
- 低风险变更不应消耗同样的资源
分层标准:
| 风险等级 | 变更类型 | 审查要求 |
|---|---|---|
| 🟢 低 | 文档、注释、格式 | CI 通过即可 |
| 🟡 中 | 功能、新模块、配置 | 1 人 review |
| 🔴 高 | 安全、运行时、网关、CI | 2 人 review + 安全审查 |
具体路径判断:
# 获取变更文件列表
files=$(git diff --name-only main...HEAD)
# 高风险路径检查
if echo "$files" | grep -qE "^src/security/|^src/gateway/|^src/runtime/"; then
echo "高风险:需要 2 人审查"
exit 1
fi
3.2 审查清单 🔗
通用检查项:
- 代码是否符合 Rust 规范
- 是否有适当的错误处理
- 是否添加了必要的测试
- 文档是否更新
- 变更是否在 PR 描述中清楚说明
高风险变更额外检查:
- 是否有安全影响分析
- 是否有回滚方案
- 是否有性能影响测试
- 是否经过手动测试
3.3 如何写出易审查的 PR 🔗
PR 描述的五个要素:
## 问题
当前 Telegram webhook 在网络超时时会 panic,导致整个网关崩溃。
## 解决方案
添加超时处理,返回 503 状态码,触发指数退避重试。
## 变更内容
- `src/channels/telegram.rs`: 添加超时处理逻辑
- `src/channels/retry.rs`: 新增指数退避模块
- `tests/telegram_test.rs`: 添加超时场景测试
## 测试
- [x] 单元测试通过
- [x] 集成测试通过(Telegram sandbox)
- [x] 手动测试:模拟 10s 网络延迟,确认行为正确
## 风险与回滚
- 风险:可能影响正常响应延迟(最大重试 3 次)
- 回滚:revert 此 PR 即可
小 PR 原则:
| PR 规模 | 审查时间 | 遗漏问题概率 |
|---|---|---|
| <100 行 | 10 分钟 | 5% |
| 100-500 行 | 30 分钟 | 15% |
| 500-1000 行 | 1 小时 | 30% |
| >1000 行 | 数小时 | 50%+ |
策略:
- 一个 PR 只做一件事
- 超过 500 行考虑拆分
- 重构和功能分离
四、测试策略 🔗
4.1 测试金字塔 🔗
flowchart TB
subgraph 金字塔["测试金字塔"]
direction TB
E2E["🔺 E2E 测试
少量
慢
高保真"]
INT["▪️ 集成测试
中量
中速
中保真"]
UNIT["▫️ 单元测试
大量
快
低保真"]
end
UNIT --> INT --> E2E
ZeroClaw 的测试分布:
| 类型 | 数量 | 执行时间 | 目的 |
|---|---|---|---|
| 单元测试 | 200+ | <10s | 验证逻辑正确性 |
| 集成测试 | 50+ | ~2min | 验证模块交互 |
| E2E 测试 | 10+ | ~10min | 验证端到端流程 |
为什么这样分布?
成本收益分析:
- 单元测试:写起来快,运行快,覆盖大部分代码
- 集成测试:验证真实交互,但设置复杂
- E2E 测试:最接近真实场景,但维护成本高
4.2 单元测试最佳实践 🔗
测试命名规范:
// ✅ 好的命名:描述行为和预期
#[test]
fn parse_config_returns_error_on_invalid_toml() { }
#[test]
fn tool_registry_returns_none_for_unknown_tool() { }
// ❌ 坏的命名:不描述行为
#[test]
fn test1() { }
#[test]
fn config_test() { }
测试结构:
#[cfg(test)]
mod tests {
use super::*;
// 给定-当-那么 (Given-When-Then) 模式
#[test]
fn execute_returns_tool_result_on_success() {
// Given: 设置测试环境
let tool = ShellTool::new();
let args = json!({"command": "echo hello"});
// When: 执行操作
let result = tokio_test::block_on(tool.execute(args));
// Then: 验证结果
assert!(result.is_ok());
assert_eq!(result.unwrap().output, "hello\n");
}
}
Mock 的使用:
// 定义 Mock
struct MockProvider {
response: CompletionResponse,
}
#[async_trait]
impl Provider for MockProvider {
async fn complete(&self, _: CompletionRequest) -> Result<CompletionResponse> {
Ok(self.response.clone())
}
}
#[tokio::test]
async fn agent_uses_tool_when_requested() {
// 使用 Mock,不依赖网络
let provider = MockProvider::new(CompletionResponse::with_tool_call("shell", json!({"command": "ls"})));
let agent = Agent::new(Box::new(provider));
let result = agent.process("list files").await;
assert!(result.contains("file"));
}
为什么用 Mock?
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Mock | 快、可控、不依赖网络 | 可能与真实行为不同 | 单元测试 |
| 真实服务 | 真实 | 慢、不稳定、成本高 | 集成测试 |
4.3 集成测试 🔗
位置:tests/ 目录
// tests/channel_integration_test.rs
#[tokio::test]
async fn telegram_channel_sends_and_receives() {
// 需要 TELEGRAM_BOT_TOKEN 环境变量
let token = env::var("TELEGRAM_BOT_TOKEN").expect("need token");
let channel = TelegramChannel::new(&token);
// 发送测试消息
let message = OutgoingMessage::text("test message");
channel.send(message).await.expect("send failed");
// 验证可以通过其他方式收到(如 API 查询)
// ...
}
集成测试的挑战:
- 外部依赖:需要真实的服务(Telegram、Discord)
- 环境配置:需要 API token
- 并行执行:测试之间可能干扰
解决方案:
// 使用测试隔离
#[tokio::test]
async fn test_isolated() {
let _guard = TEST_MUTEX.lock().await; // 串行执行
// ...
}
// 条件编译跳过
#[tokio::test]
#[ignore = "needs telegram token"]
async fn telegram_test() { }
// 运行:cargo test -- --ignored
4.4 E2E 测试 🔗
测试整个工作流:
# 启动 ZeroClaw
./target/release/zeroclaw service &
ZEROCALW_PID=$!
# 发送测试消息
curl -X POST http://localhost:8080/webhook \
-H "Content-Type: application/json" \
-d '{"message": "hello", "chat_id": "test"}'
# 验证响应
# ...
# 清理
kill $ZEROCALW_PID
为什么 E2E 测试数量少?
- 运行慢(需要启动完整服务)
- 维护成本高(依赖环境配置)
- 不稳定(网络、服务状态)
策略:
- 只测试核心流程
- 使用 Docker 保证环境一致
- 定期运行而非每次提交
五、代码质量保障 🔗
5.1 自动化检查矩阵 🔗
# 格式检查
cargo fmt --all -- --check
# 静态分析
cargo clippy --all-targets -- -D warnings
# 安全检查
cargo audit
cargo deny check
# 测试
cargo test
# 文档检查
cargo doc --no-deps
为什么用 -D warnings?
将 warning 视为 error,强制在 CI 中修复。
例外情况:
// 如果某个 warning 是误报,可以显式允许
#[allow(clippy::too_many_arguments)]
fn some_function(a: A, b: B, c: C, ...) { }
5.2 预提交钩子(Pre-commit Hooks) 🔗
为什么推荐预提交钩子?
在提交前自动运行检查,避免提交不合规范的代码。
推荐配置:
# .git/hooks/pre-commit
#!/bin/bash
# 格式化
cargo fmt -- --check || exit 1
# 静态检查
cargo clippy -- -D warnings || exit 1
# 测试(只运行受影响的)
cargo test --lib || exit 1
echo "Pre-commit checks passed!"
注意:
- 钩子应该是快速的(<30 秒)
- 复杂的检查留给 CI
- 提供跳过选项(
git commit --no-verify)
5.3 代码覆盖率 🔗
# 生成覆盖率报告
cargo tarpaulin --out Html
# 查看报告
open tarpaulin-report.html
目标覆盖率:
- 核心业务逻辑:>80%
- 工具执行:>70%
- 错误处理:>60%
- UI/配置:>50%
注意:覆盖率不是目标,质量才是。
六、CI/CD 工作流 🔗
6.1 GitHub Actions 工作流 🔗
# 简化版 CI 流程
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
# 检查
- run: cargo fmt --all -- --check
- run: cargo clippy --all-targets -- -D warnings
- run: cargo audit
# 测试
- run: cargo test --all-features
# 构建
- run: cargo build --release --locked
# 上传产物
- uses: actions/upload-artifact@v4
with:
name: zeroclaw-linux
path: target/release/zeroclaw
6.2 矩阵构建 🔗
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04
- target: x86_64-apple-darwin
os: macos-latest
为什么用矩阵?
- 并行构建
- 统一配置
- 易于扩展新平台
6.3 发布流程 🔗
flowchart LR
TAG["推送标签 v1.0.0"] --> BUILD["构建多平台二进制"]
BUILD --> TEST["运行测试套件"]
TEST --> RELEASE["创建 GitHub Release"]
RELEASE --> DOCKER["推送 Docker 镜像"]
DOCKER --> NOTIFY["通知用户"]
自动化发布的好处:
- 可复现:每次发布流程相同
- 可追溯:每个版本有对应的 CI 记录
- 安全:减少人工操作错误
七、故障排查 🔗
7.1 CI 失败常见原因 🔗
| 失败类型 | 可能原因 | 解决方案 |
|---|---|---|
| 格式检查失败 | 未运行 cargo fmt | 本地运行 fmt 后提交 |
| Clippy 警告 | 代码风格问题 | 修复或显式允许 |
| 测试超时 | 网络依赖 | 使用 mock 或重试 |
| 构建失败 | 依赖问题 | cargo update 或检查 lock |
| 安全审计 | 依赖漏洞 | cargo update 或更换依赖 |
7.2 本地复现 CI 问题 🔗
# 使用相同环境
docker run -v $(pwd):/workspace -w /workspace rust:1.75 bash
# 在容器中运行 CI 命令
cargo build --release --locked
附录:快速参考 🔗
日常开发命令 🔗
# 创建功能分支
git checkout -b feature/my-feature
# 开发循环
cargo build # 快速编译
cargo test # 运行测试
cargo fmt # 格式化
git commit -m "feat(module): description"
# 提交前检查
./scripts/check.sh # 运行所有检查
# 推送并创建 PR
git push origin feature/my-feature
PR 模板 🔗
## 变更类型
- [ ] feat: 新功能
- [ ] fix: bug 修复
- [ ] doc: 文档更新
- [ ] refactor: 重构
- [ ] perf: 性能优化
## 测试
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 手动测试验证
## 风险
- [ ] 低:文档/格式化
- [ ] 中:功能增强
- [ ] 高:安全/运行时变更