WeChat Bot自动化测试:CI/CD流程配置
引言:为什么自动化测试与CI/CD对微信机器人至关重要
在微信机器人开发中,自动化测试和CI/CD流程是保障项目质量和效率的关键环节。随着业务复杂度提升,手动测试难以覆盖所有场景,而CI/CD能够实现代码提交即测试、测试通过即部署的自动化流程。本文将以WeChat Bot项目为例,详细介绍如何构建完整的自动化测试与CI/CD体系。
项目测试现状分析
现有测试脚本解析
WeChat Bot项目已具备基础测试能力,在package.json中定义了多个测试脚本:
{
"scripts": {
"test": "node ./src/wechaty/testMessage.js",
"test-openai": "node ./src/openai/__test__.js",
"test-xunfei": "node ./src/xunfei/__test__.js",
"test-kimi": "node ./src/kimi/__test__.js",
"test-dify": "node ./src/dify/__test__.js"
}
}
这些脚本针对不同AI服务提供商(OpenAI、讯飞、Kimi等)实现了基础测试,例如DeepSeek的测试脚本src/deepseek-free/__test__.js:
import { getDeepSeekFreeReply } from './index.js'
async function testMessage() {
const message = await getDeepSeekFreeReply('hello')
console.log('🌸🌸🌸 / message: ', message)
}
testMessage()
当前测试体系的局限性
- 缺乏自动化触发机制:测试需手动执行,无法在代码变更时自动触发
- 无环境隔离:开发环境与测试环境未分离,易受本地配置影响
- 测试覆盖率不足:仅覆盖API调用,缺乏端到端测试和集成测试
- 部署流程手动化:Docker部署需手动构建和推送镜像
CI/CD流程设计与实现
CI/CD架构设计
环境准备与依赖配置
1. 必要依赖安装
确保项目中已安装测试和CI相关依赖:
# 安装测试工具
npm install --save-dev jest supertest
# 安装代码质量工具
npm install --save-dev eslint prettier husky lint-staged
2. 测试环境变量配置
创建CI专用环境变量文件.env.ci:
# CI环境专用配置
LOG_LEVEL=error
TEST_MODE=true
# AI服务测试密钥(使用CI/CD平台的secret存储)
DEEPSEEK_FREE_TOKEN=${{ secrets.DEEPSEEK_FREE_TOKEN }}
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
GitHub Actions工作流配置
在项目中创建.github/workflows/ci-cd.yml文件:
name: WeChat Bot CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
quality-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Code formatting check
run: npm run format -- --check
- name: Lint code
run: eslint . --ext .js,.mjs
unit-test:
needs: quality-check
runs-on: ubuntu-latest
strategy:
matrix:
service: [openai, xunfei, kimi, dify, deepseek-free]
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Create .env file
run: |
echo "DEEPSEEK_FREE_TOKEN=${{ secrets.DEEPSEEK_FREE_TOKEN }}" > .env
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" >> .env
echo "XUNFEI_APP_ID=${{ secrets.XUNFEI_APP_ID }}" >> .env
echo "XUNFEI_API_KEY=${{ secrets.XUNFEI_API_KEY }}" >> .env
echo "XUNFEI_API_SECRET=${{ secrets.XUNFEI_API_SECRET }}" >> .env
echo "KIMI_API_KEY=${{ secrets.KIMI_API_KEY }}" >> .env
echo "DIFY_API_KEY=${{ secrets.DIFY_API_KEY }}" >> .env
- name: Run ${{ matrix.service }} tests
run: npm run test-${{ matrix.service }}
integration-test:
needs: unit-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run integration tests
run: npm run test
build-and-deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [unit-test, integration-test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: username/wechat-bot:latest,username/wechat-bot:${{ github.sha }}
- name: Deploy to production
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/wechat-bot
docker-compose pull
docker-compose up -d
测试策略与实现
单元测试增强
以DeepSeek服务为例,改进测试脚本src/deepseek-free/__test__.js:
import { getDeepSeekFreeReply } from './index.js';
import axios from 'axios';
// Mock axios
jest.mock('axios');
describe('DeepSeek Free API Tests', () => {
beforeEach(() => {
process.env.DEEPSEEK_FREE_TOKEN = 'test-token';
process.env.DEEPSEEK_FREE_URL = 'https://api.deepseek.com/chat/completions';
process.env.DEEPSEEK_FREE_MODEL = 'deepseek-chat';
});
afterEach(() => {
jest.clearAllMocks();
});
test('should return valid response for simple query', async () => {
// Mock successful response
axios.post.mockResolvedValue({
data: {
choices: [{
message: {
content: 'Hello from DeepSeek!'
}
}]
}
});
const response = await getDeepSeekFreeReply('Hello');
expect(response).toBe('Hello from DeepSeek!');
expect(axios.post).toHaveBeenCalledWith(
'https://api.deepseek.com/chat/completions',
expect.objectContaining({
model: 'deepseek-chat',
messages: expect.any(Array)
}),
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer test-token'
})
})
);
});
test('should handle API errors gracefully', async () => {
// Mock error response
axios.post.mockRejectedValue(new Error('API request failed'));
const response = await getDeepSeekFreeReply('Hello');
expect(response).toContain('Error');
expect(response).toContain('API request failed');
});
});
集成测试实现
创建tests/integration/wechaty.test.js文件:
import { WechatyBuilder } from 'wechaty';
import { PuppetMock } from 'wechaty-puppet-mock';
import { onMessage } from '../../src/wechaty/onMessage.js';
describe('WeChat Bot Integration Tests', () => {
let bot;
beforeAll(async () => {
bot = WechatyBuilder.build({
name: 'test-bot',
puppet: new PuppetMock(),
});
await bot.start();
});
afterAll(async () => {
await bot.stop();
});
test('should auto-reply to whitelisted contact', async () => {
// Create mock contact and message
const contact = bot.Contact.load('test-contact');
await contact.ready();
// Mock contact is in whitelist
process.env.ALIAS_WHITELIST = 'test-contact';
// Mock message
const message = bot.Message.load('test-message-id');
message.say = jest.fn();
message.talker().mockResolvedValue(contact);
message.text().mockReturnValue('Hello bot');
// Trigger message handler
await onMessage(message);
// Verify reply
expect(message.say).toHaveBeenCalled();
});
test('should not reply to non-whitelisted contact', async () => {
// Create mock contact and message
const contact = bot.Contact.load('stranger');
await contact.ready();
// Mock contact not in whitelist
process.env.ALIAS_WHITELIST = 'test-contact';
// Mock message
const message = bot.Message.load('test-message-id-2');
message.say = jest.fn();
message.talker().mockResolvedValue(contact);
message.text().mockReturnValue('Hello bot');
// Trigger message handler
await onMessage(message);
// Verify no reply
expect(message.say).not.toHaveBeenCalled();
});
});
端到端测试方案
使用Puppeteer模拟微信网页版登录流程,创建tests/e2e/login.test.js:
import puppeteer from 'puppeteer';
describe('WeChat Bot E2E Tests', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
});
test('should start bot and show QR code', async () => {
// Start bot in test mode
const botProcess = require('child_process').spawn('npm', ['run', 'dev'], {
env: { ...process.env, TEST_MODE: 'true' }
});
// Wait for QR code output
let qrCodeOutput = '';
for await (const data of botProcess.stdout) {
qrCodeOutput += data.toString();
if (qrCodeOutput.includes('QR Code')) break;
}
// Verify QR code is generated
expect(qrCodeOutput).toContain('QR Code');
expect(qrCodeOutput).toContain('Scan with WeChat');
// Stop bot
botProcess.kill();
}, 30000);
});
Docker容器化与部署优化
Dockerfile优化
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Test stage
FROM builder AS tester
RUN npm ci
RUN npm test
# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/src ./src
COPY --from=builder /app/cli.js ./
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["npm", "run", "start"]
Docker Compose配置
创建docker-compose.yml用于本地开发和测试:
version: '3.8'
services:
wechat-bot:
build:
context: .
target: builder
volumes:
- .:/app
- /app/node_modules
environment_file:
- .env
command: npm run dev
ports:
- "3000:3000"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 3s
retries: 3
测试报告与质量监控
测试覆盖率报告
在package.json中添加覆盖率报告脚本:
{
"scripts": {
"test:coverage": "jest --coverage",
"test:report": "jest --coverage && open coverage/lcov-report/index.html"
}
}
集成代码质量平台
在CI流程中添加SonarCloud代码质量检查:
# 在.github/workflows/ci-cd.yml中添加
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
projectBaseDir: .
持续监控与告警
健康检查接口实现
创建src/health.js:
import http from 'http';
export function startHealthServer() {
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
timestamp: new Date().toISOString(),
services: {
wechaty: global.bot?.isLoggedIn ? 'connected' : 'disconnected',
deepseek: global.deepseekStatus || 'unknown'
}
}));
} else {
res.writeHead(404);
res.end();
}
});
server.listen(3000, () => {
console.log('Health check server running on port 3000');
});
return server;
}
错误监控与告警
集成Sentry进行错误跟踪:
// src/utils/errorHandler.js
import * as Sentry from '@sentry/node';
export function initErrorMonitoring() {
if (process.env.SENTRY_DSN) {
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 1.0,
});
console.log('Sentry error monitoring initialized');
}
}
export function captureError(error) {
if (process.env.SENTRY_DSN) {
Sentry.captureException(error);
}
console.error('Error:', error);
}
总结与最佳实践
CI/CD流程优化建议
- 并行测试执行:在CI配置中使用矩阵策略并行运行不同服务的测试
- 缓存依赖:配置npm和Docker缓存减少构建时间
- 增量测试:只运行变更文件相关的测试用例
- 部署策略:采用蓝绿部署或金丝雀发布降低风险
- 安全扫描:集成依赖漏洞扫描工具如Snyk
自动化测试最佳实践
下一步计划
- 测试覆盖率提升:目标达到80%以上代码覆盖率
- 性能测试:添加负载测试和性能基准测试
- 混沌工程:引入故障注入测试提高系统韧性
- 多环境部署:完善开发、测试、预发、生产环境的CI/CD流程
- 自动化文档:通过测试生成API文档和使用示例
通过实施本文所述的CI/CD流程和自动化测试策略,WeChat Bot项目将实现代码提交即测试、测试通过即部署的全自动化流程,大幅提升开发效率和系统稳定性,同时降低人工测试成本和人为错误风险。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



