ZeroClaw-11-开发工作流与测试深度解析

· 4216字 · 9分钟

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 的核心规则

  1. main 分支始终可部署
  2. 新功能从 main 创建分支
  3. 通过 Pull Request 合并回 main
  4. 合并前必须通过 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/<...>    # 依赖更新(自动)

为什么要规范命名?

  1. 一目了然:从分支名就知道目的
  2. 自动分类:可以通过脚本统计各类 PR 数量
  3. 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]

为什么使用规范提交?

  1. 自动生成 CHANGELOG
# feat 提交 → 自动包含在 Features 部分
# fix 提交 → 自动包含在 Bug Fixes 部分
# BREAKING CHANGE → 自动包含在 Breaking Changes 部分
  1. 版本号自动化
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)
  1. 搜索和过滤
# 快速找到所有 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

为什么区分 refactorfix

  • 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

提交信息应该回答

  1. 做了什么(What)
  2. 为什么做(Why)
  3. 有什么影响(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 查询)
    // ...
}

集成测试的挑战

  1. 外部依赖:需要真实的服务(Telegram、Discord)
  2. 环境配置:需要 API token
  3. 并行执行:测试之间可能干扰

解决方案

// 使用测试隔离
#[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: 性能优化

## 测试
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 手动测试验证

## 风险
- [ ] 低:文档/格式化
- [ ] 中:功能增强
- [ ] 高:安全/运行时变更