🧪 OpenClaw 自动化测试与 CI/CD

📅 2026年5月19日 | 🏷️ 测试专题 | ⏱️ 阅读时间:20分钟

让 Agent 开发更可靠——单元测试、集成测试、E2E测试、CI/CD 一条龙。

🎬 为什么需要自动化测试?

凌晨2点56分,我部署了一个新 Skill,结果导致整个 Agent 崩溃——因为我忘了测试边界情况。

就像周星驰电影《破坏之王》里的"输了就要变女人"——没测试就上线,后果很严重。自动化测试就是你的"必胜客",让你稳稳地赢。

🔍 测试金字塔

           /\
          /  \    E2E测试 (少量,关键路径)
         /____\
        /      \   集成测试 (中等,API接口)
       /__________\   单元测试 (大量,函数级别)
      /____________\
  • 单元测试:测试单个函数/模块(快速、隔离)
  • 集成测试:测试模块间交互(API、数据库)
  • E2E测试:测试完整用户流程(慢速、真实)

🧪 单元测试(Unit Test)

配置 Jest(Node.js)

# 安装依赖
npm install --save-dev jest @types/jest

# package.json 配置
{
    "scripts": {
        "test": "jest",
        "test:watch": "jest --watch",
        "test:coverage": "jest --coverage"
    },
    "jest": {
        "testEnvironment": "node",
        "coveragePathIgnorePatterns": ["/node_modules/", "/test/"]
    }
}

编写单元测试

// math.js - 被测试模块
function add(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('Both arguments must be numbers');
    }
    return a + b;
}

module.exports = { add };

// test/math.test.js - 测试文件
const { add } = require('../math.js');

describe('add function', () => {
    test('adds two numbers correctly', () => {
        expect(add(2, 3)).toBe(5);
        expect(add(-1, 1)).toBe(0);
        expect(add(0.1, 0.2)).toBeCloseTo(0.3);
    });

    test('throws error for non-numbers', () => {
        expect(() => add('2', 3)).toThrow('Both arguments must be numbers');
        expect(() => add(2, '3')).toThrow('Both arguments must be numbers');
        expect(() => add(null, undefined)).toThrow();
    });
});

// 运行测试
npm test

// 输出:
// PASS  test/math.test.js
// ✓ adds two numbers correctly (5 ms)
// ✓ throws error for non-numbers (3 ms)
// Test Suites: 1 passed, 1 total
// Tests:       2 passed, 2 total
💡 最佳实践: 单元测试要"快、独立、可重复"。每个测试只测一件事,不依赖外部状态。

🔗 集成测试(Integration Test)

测试 API 接口

// test/api.test.js
const request = require('supertest');
const app = require('../app.js');  // Express app

describe('API Integration Tests', () => {
    test('GET /api/skills returns list', async () => {
        const response = await request(app)
            .get('/api/skills')
            .expect('Content-Type', /json/)
            .expect(200);

        expect(response.body).toBeInstanceOf(Array);
        expect(response.body.length).toBeGreaterThan(0);
    });

    test('POST /api/skills creates new skill', async () => {
        const newSkill = {
            name: 'test-skill',
            description: 'A test skill'
        };

        const response = await request(app)
            .post('/api/skills')
            .send(newSkill)
            .expect(201);

        expect(response.body.name).toBe('test-skill');
    });
});

测试数据库交互

// test/db.test.js
const db = require('../db.js');

describe('Database Integration', () => {
    beforeAll(async () => {
        await db.connect();  // 连接测试数据库
    });

    afterAll(async () => {
        await db.disconnect();
    });

    test('inserts and retrieves record', async () => {
        const record = { key: 'test', value: '123' };
        await db.insert('test_table', record);
        
        const result = await db.query('SELECT * FROM test_table WHERE key = ?', ['test']);
        expect(result[0].value).toBe('123');
    });
});

🌐 E2E 测试(End-to-End)

使用 Puppeteer 测试 Web UI

# 安装 Puppeteer
npm install --save-dev puppeteer

// test/e2e.spec.js
const puppeteer = require('puppeteer');

describe('E2E: OpenClaw Web UI', () => {
    let browser, page;

    beforeAll(async () => {
        browser = await puppeteer.launch({ headless: true });
        page = await browser.newPage();
    });

    afterAll(async () => {
        await browser.close();
    });

    test('user can generate image', async () => {
        await page.goto('https://miaoquai.com/tools/openclaw-skills-...');
        
        // 输入 prompt
        await page.type('#prompt-input', 'a cat on moon');
        
        // 点击生成按钮
        await page.click('#generate-btn');
        
        // 等待结果
        await page.waitForSelector('#result-image', { timeout: 30000 });
        
        // 验证结果
        const imageSrc = await page.$eval('#result-image', img => img.src);
        expect(imageSrc).toMatch(/^https?:\/\//);
    }, 60000);  // 60秒超时
});

🚀 CI/CD 配置(GitHub Actions)

# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        node-version: [18.x, 20.x, 22.x]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run linter
      run: npm run lint
    
    - name: Run unit tests
      run: npm run test:coverage
    
    - name: Upload coverage
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/coverage-final.json
    
    - name: Run integration tests
      run: npm run test:integration
      env:
        DATABASE_URL: ${{ secrets.TEST_DB_URL }}

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Build
      run: |
        npm ci
        npm run build
    
    - name: Deploy to production
      uses: easingthemes/ssh-deploy@main
      with:
        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
        remote-host: ${{ secrets.DEPLOY_HOST }}
        source: "dist/"
        target: "/var/www/miaoquai/"

🎯 CI/CD 流程说明

  1. 代码提交 → 触发 GitHub Actions
  2. 多版本测试 → Node 18/20/22 都跑一遍
  3. 代码检查 → ESLint 检查代码质量
  4. 单元测试 → Jest 跑测试 + 覆盖率
  5. 集成测试 → 测试数据库、API 等
  6. 构建部署 → 测试通过后自动部署

📊 测试覆盖率目标

# 查看覆盖率报告
npm run test:coverage

# 输出示例:
# ----------|---------|----------|---------|---------
# File      | % Stmts | % Branch | % Funcs | % Lines
# ----------|---------|----------|---------|---------
# math.js   |   100   |    80    |   100   |   100
# api.js    |    95   |    85    |    90   |    95
# db.js     |    88   |    75    |    85   |    88
# ----------|---------|----------|---------|---------
# All files |    94   |    80    |    92   |    94

# 配置覆盖率门槛(package.json)
{
    "jest": {
        "coverageThreshold": {
            "global": {
                "statements": 80,
                "branches": 70,
                "functions": 80,
                "lines": 80
            }
        }
    }
}
💡 覆盖率建议: 单元测试 80%+,核心模块 90%+,集成测试覆盖关键流程。

🌟 总结

凌晨3点37分,我看着 CI/CD 绿灯亮起,心里踏实了——代码再也不会"裸奔"上线。

你已经学会了:

记住:测试不是"额外工作",是代码质量的"保险丝"。