8.9 自动化测试实战 🤖
"自动化测试不是为了取代人工测试,而是为了让开发者从重复劳动中解放出来,专注于创造价值。"
想象自动化测试就像是给你的代码配备了一支24小时待命的质检团队。每当有新代码提交,这支团队就会立刻行动,全面检查代码质量,确保没有任何问题溜过去。
CI/CD集成:让测试自动运转 ⚙️
GitHub Actions工作流配置
yaml
# 🌟 完整的CI/CD工作流配置
# .github/workflows/mcp-ci-cd.yml
name: MCP Server CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
# 每天凌晨2点运行完整测试套件
- cron: '0 2 * * *'
env:
NODE_VERSION: '18'
PYTHON_VERSION: '3.11'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 1. 代码质量检查
code-quality:
runs-on: ubuntu-latest
name: 代码质量检查
steps:
- name: 检出代码
uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史用于SonarQube分析
- name: 设置Node.js环境
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: ESLint代码检查
run: |
echo "🔍 运行ESLint代码检查..."
npm run lint:check
- name: Prettier格式检查
run: |
echo "💅 检查代码格式..."
npm run format:check
- name: TypeScript类型检查
run: |
echo "🔤 运行TypeScript类型检查..."
npm run type-check
- name: 安全漏洞扫描
run: |
echo "🛡️ 扫描安全漏洞..."
npm audit --audit-level=moderate
- name: 依赖许可证检查
run: |
echo "📄 检查依赖许可证..."
npx license-checker --onlyAllow "MIT;Apache-2.0;BSD-3-Clause;ISC"
- name: 上传代码质量报告
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_TYPESCRIPT_ES: true
VALIDATE_JAVASCRIPT_ES: true
VALIDATE_JSON: true
VALIDATE_YAML: true
# 2. 单元测试
unit-tests:
runs-on: ubuntu-latest
name: 单元测试
needs: code-quality
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 运行单元测试
run: |
echo "🧪 运行单元测试 (Node.js ${{ matrix.node-version }})..."
npm run test:unit -- --coverage --maxWorkers=2
env:
CI: true
- name: 上传测试覆盖率
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
flags: unit-tests
name: codecov-unit-${{ matrix.node-version }}
fail_ci_if_error: false
- name: 上传测试结果
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: 单元测试结果 (Node.js ${{ matrix.node-version }})
path: 'test-results/junit.xml'
reporter: jest-junit
# 3. 集成测试
integration-tests:
runs-on: ubuntu-latest
name: 集成测试
needs: unit-tests
services:
# Redis服务用于缓存测试
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
# PostgreSQL服务用于数据库测试
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: mcp_test
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置Node.js环境
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 等待服务就绪
run: |
echo "⏳ 等待数据库和缓存服务就绪..."
timeout 30 bash -c 'until nc -z localhost 5432; do sleep 1; done'
timeout 30 bash -c 'until nc -z localhost 6379; do sleep 1; done'
echo "✅ 所有服务已就绪"
- name: 初始化测试数据库
run: |
echo "📊 初始化测试数据库..."
npm run db:migrate:test
npm run db:seed:test
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/mcp_test
REDIS_URL: redis://localhost:6379
- name: 运行集成测试
run: |
echo "🔗 运行集成测试..."
npm run test:integration -- --maxWorkers=1
env:
CI: true
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/mcp_test
REDIS_URL: redis://localhost:6379
- name: 上传集成测试覆盖率
uses: codecov/codecov-action@v3
with:
file: ./coverage/integration-lcov.info
flags: integration-tests
name: codecov-integration
fail_ci_if_error: false
# 4. 端到端测试
e2e-tests:
runs-on: ubuntu-latest
name: 端到端测试
needs: integration-tests
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置Node.js环境
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 安装Playwright浏览器
run: npx playwright install --with-deps
- name: 构建应用
run: |
echo "🏗️ 构建生产版本..."
npm run build
- name: 启动应用服务器
run: |
echo "🚀 启动应用服务器..."
npm run start:test &
# 等待服务器启动
timeout 60 bash -c 'until curl -f http://localhost:3000/health; do sleep 2; done'
echo "✅ 应用服务器已启动"
env:
NODE_ENV: test
PORT: 3000
- name: 运行端到端测试
run: |
echo "🌐 运行端到端测试..."
npm run test:e2e
env:
BASE_URL: http://localhost:3000
- name: 上传Playwright报告
uses: actions/upload-artifact@v3
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: 上传E2E测试截图
uses: actions/upload-artifact@v3
if: failure()
with:
name: e2e-screenshots
path: test-results/
retention-days: 7
# 5. 性能测试
performance-tests:
runs-on: ubuntu-latest
name: 性能测试
needs: integration-tests
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置Node.js环境
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 构建应用
run: npm run build
- name: 启动应用
run: |
npm run start:prod &
timeout 60 bash -c 'until curl -f http://localhost:3000/health; do sleep 2; done'
env:
NODE_ENV: production
PORT: 3000
- name: 运行负载测试
run: |
echo "⚡ 运行负载测试..."
npm run test:load
- name: 运行压力测试
run: |
echo "💪 运行压力测试..."
npm run test:stress
- name: 分析性能指标
run: |
echo "📊 分析性能指标..."
npm run analyze:performance
- name: 上传性能报告
uses: actions/upload-artifact@v3
with:
name: performance-report
path: performance-report/
retention-days: 30
# 6. 安全测试
security-tests:
runs-on: ubuntu-latest
name: 安全测试
needs: code-quality
steps:
- name: 检出代码
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 运行Trivy漏洞扫描
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: 上传Trivy扫描结果
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: 运行Semgrep安全分析
run: |
echo "🔒 运行Semgrep安全分析..."
python -m pip install semgrep
semgrep --config=auto --json --output=semgrep-report.json .
- name: 上传安全测试报告
uses: actions/upload-artifact@v3
with:
name: security-report
path: |
trivy-results.sarif
semgrep-report.json
retention-days: 30
# 7. 构建和发布
build-and-publish:
runs-on: ubuntu-latest
name: 构建和发布
needs: [unit-tests, integration-tests, e2e-tests, security-tests]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置Node.js环境
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 构建应用
run: |
echo "🏗️ 构建生产版本..."
npm run build
- name: 登录容器注册表
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 提取元数据
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: 构建并推送Docker镜像
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: 部署到暂存环境
if: github.ref == 'refs/heads/main'
run: |
echo "🚀 部署到暂存环境..."
# 这里可以添加部署脚本
echo "部署完成!"
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
STAGING_SERVER: ${{ secrets.STAGING_SERVER }}
# 8. 通知和报告
notify:
runs-on: ubuntu-latest
name: 通知和报告
needs: [build-and-publish]
if: always()
steps:
- name: 获取工作流状态
id: workflow-status
run: |
if [[ "${{ needs.build-and-publish.result }}" == "success" ]]; then
echo "status=success" >> $GITHUB_OUTPUT
echo "message=🎉 CI/CD流水线执行成功!" >> $GITHUB_OUTPUT
echo "color=good" >> $GITHUB_OUTPUT
else
echo "status=failure" >> $GITHUB_OUTPUT
echo "message=❌ CI/CD流水线执行失败!" >> $GITHUB_OUTPUT
echo "color=danger" >> $GITHUB_OUTPUT
fi
- name: 发送Slack通知
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ steps.workflow-status.outputs.status }}
text: |
${{ steps.workflow-status.outputs.message }}
📊 **执行摘要:**
• 代码质量检查: ${{ needs.code-quality.result }}
• 单元测试: ${{ needs.unit-tests.result }}
• 集成测试: ${{ needs.integration-tests.result }}
• 端到端测试: ${{ needs.e2e-tests.result }}
• 安全测试: ${{ needs.security-tests.result }}
• 构建发布: ${{ needs.build-and-publish.result }}
🔗 查看详情: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
- name: 更新README徽章
if: github.ref == 'refs/heads/main'
run: |
echo "📛 更新README状态徽章..."
# 可以使用shields.io API更新徽章状态
echo "徽章已更新"
本地开发环境配置
json
// 🌟 package.json 测试脚本配置
{
"name": "mcp-server",
"version": "1.0.0",
"scripts": {
// 开发环境脚本
"dev": "tsx watch src/index.ts",
"build": "tsc && esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js",
"start": "node dist/index.js",
"start:prod": "NODE_ENV=production node dist/index.js",
"start:test": "NODE_ENV=test node dist/index.js",
// 代码质量脚本
"lint": "eslint src/ tests/ --ext .ts,.js",
"lint:fix": "eslint src/ tests/ --ext .ts,.js --fix",
"lint:check": "eslint src/ tests/ --ext .ts,.js --max-warnings 0",
"format": "prettier --write 'src/**/*.{ts,js,json}' 'tests/**/*.{ts,js,json}'",
"format:check": "prettier --check 'src/**/*.{ts,js,json}' 'tests/**/*.{ts,js,json}'",
"type-check": "tsc --noEmit",
// 测试脚本
"test": "npm run test:unit && npm run test:integration",
"test:unit": "jest --testPathPattern=tests/unit --coverage",
"test:integration": "jest --testPathPattern=tests/integration --runInBand",
"test:e2e": "playwright test",
"test:watch": "jest --watch --testPathPattern=tests/unit",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --no-cache",
// 性能测试脚本
"test:load": "k6 run tests/performance/load-test.js",
"test:stress": "k6 run tests/performance/stress-test.js",
"analyze:performance": "node scripts/analyze-performance.js",
// 数据库脚本
"db:migrate": "knex migrate:latest",
"db:migrate:test": "NODE_ENV=test knex migrate:latest",
"db:seed": "knex seed:run",
"db:seed:test": "NODE_ENV=test knex seed:run",
"db:rollback": "knex migrate:rollback",
// 部署脚本
"docker:build": "docker build -t mcp-server .",
"docker:run": "docker run -p 3000:3000 mcp-server",
"deploy:staging": "node scripts/deploy-staging.js",
"deploy:prod": "node scripts/deploy-production.js",
// 实用工具脚本
"clean": "rimraf dist coverage test-results",
"precommit": "lint-staged",
"prepare": "husky install",
"release": "standard-version"
},
"devDependencies": {
// 测试框架
"@jest/globals": "^29.7.0",
"jest": "^29.7.0",
"jest-junit": "^16.0.0",
"ts-jest": "^29.1.1",
"@playwright/test": "^1.40.0",
// 代码质量工具
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.6.0",
"prettier": "^3.1.0",
// Git钩子
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
// 构建工具
"typescript": "^5.3.2",
"tsx": "^4.6.0",
"esbuild": "^0.19.8",
"rimraf": "^5.0.5",
// 性能测试
"k6": "^0.48.0",
// 其他工具
"standard-version": "^9.5.0",
"license-checker": "^25.0.1"
},
"lint-staged": {
"*.{ts,js}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"roots": ["<rootDir>/src", "<rootDir>/tests"],
"testMatch": [
"**/__tests__/**/*.ts",
"**/?(*.)+(spec|test).ts"
],
"collectCoverageFrom": [
"src/**/*.ts",
"!src/**/*.d.ts",
"!src/types/**/*",
"!src/index.ts"
],
"coverageReporters": [
"text",
"lcov",
"html",
"json-summary"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
},
"setupFilesAfterEnv": ["<rootDir>/tests/setup.ts"],
"reporters": [
"default",
["jest-junit", {
"outputDirectory": "test-results",
"outputName": "junit.xml"
}]
]
}
}
高级测试配置
typescript
// 🌟 Jest测试环境配置
// tests/setup.ts
import { jest } from '@jest/globals';
import { setupServer } from 'msw/node';
import { rest } from 'msw';
// 全局测试设置
global.console = {
...console,
// 在测试中静默某些日志
log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: console.warn,
error: console.error,
};
// Mock服务器设置
const server = setupServer(
// 模拟天气API
rest.get('https://api.weather.com/v1/weather', (req, res, ctx) => {
const location = req.url.searchParams.get('location');
return res(
ctx.json({
location,
temperature: 25,
description: '晴天',
humidity: 60,
timestamp: new Date().toISOString()
})
);
}),
// 模拟认证API
rest.post('https://api.auth.com/v1/token', (req, res, ctx) => {
return res(
ctx.json({
access_token: 'mock_token_12345',
token_type: 'Bearer',
expires_in: 3600
})
);
})
);
// 测试生命周期钩子
beforeAll(() => {
console.log('🚀 启动测试环境...');
server.listen({ onUnhandledRequest: 'error' });
});
beforeEach(() => {
// 重置所有Mock
jest.clearAllMocks();
});
afterEach(() => {
// 重置服务器处理器
server.resetHandlers();
});
afterAll(() => {
console.log('🛑 关闭测试环境...');
server.close();
});
// 全局测试工具
export const testUtils = {
// 等待异步操作完成
waitFor: (condition: () => boolean, timeout = 5000): Promise<void> => {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
if (condition()) {
resolve();
} else if (Date.now() - startTime > timeout) {
reject(new Error(`等待超时:${timeout}ms`));
} else {
setTimeout(check, 100);
}
};
check();
});
},
// 创建模拟MCP客户端
createMockClient: () => ({
connect: jest.fn().mockResolvedValue(true),
disconnect: jest.fn().mockResolvedValue(undefined),
callTool: jest.fn(),
listTools: jest.fn().mockResolvedValue({
tools: [
{
name: 'get_weather',
description: '获取天气信息',
inputSchema: {
type: 'object',
properties: {
location: { type: 'string' }
}
}
}
]
})
}),
// 模拟数据库连接
createMockDatabase: () => ({
query: jest.fn(),
transaction: jest.fn(),
close: jest.fn()
})
};
// 测试数据工厂
export const testDataFactory = {
createUser: (overrides = {}) => ({
id: 'user_123',
username: 'testuser',
email: 'test@example.com',
createdAt: new Date(),
...overrides
}),
createWeatherData: (overrides = {}) => ({
location: '北京',
temperature: 25,
description: '晴天',
humidity: 60,
timestamp: new Date(),
...overrides
}),
createToolCall: (overrides = {}) => ({
toolName: 'get_weather',
parameters: { location: '北京' },
requestId: 'req_123',
timestamp: new Date(),
...overrides
})
};
Playwright端到端测试配置
typescript
// 🌟 Playwright配置
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'test-results/e2e-results.xml' }],
['github']
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
],
webServer: {
command: 'npm run start:test',
url: 'http://localhost:3000/health',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
});
// tests/e2e/mcp-workflow.spec.ts
import { test, expect } from '@playwright/test';
test.describe('MCP工作流测试', () => {
test.beforeEach(async ({ page }) => {
// 每个测试前的设置
await page.goto('/');
});
test('用户完整工作流', async ({ page }) => {
// 1. 用户登录
await page.click('[data-testid="login-button"]');
await page.fill('[data-testid="username"]', 'testuser');
await page.fill('[data-testid="password"]', 'testpass');
await page.click('[data-testid="submit-login"]');
// 验证登录成功
await expect(page.locator('[data-testid="dashboard"]')).toBeVisible();
// 2. 导航到工具页面
await page.click('[data-testid="tools-nav"]');
await expect(page.locator('[data-testid="tools-list"]')).toBeVisible();
// 3. 选择天气工具
await page.click('[data-testid="tool-weather"]');
await expect(page.locator('[data-testid="tool-form"]')).toBeVisible();
// 4. 填写参数并执行
await page.fill('[data-testid="location-input"]', '北京');
await page.click('[data-testid="execute-button"]');
// 5. 验证结果
await expect(page.locator('[data-testid="tool-result"]')).toBeVisible();
await expect(page.locator('[data-testid="tool-result"]')).toContainText('北京');
await expect(page.locator('[data-testid="tool-result"]')).toContainText('温度');
// 6. 验证历史记录
await page.click('[data-testid="history-nav"]');
await expect(page.locator('[data-testid="history-list"]')).toBeVisible();
await expect(page.locator('[data-testid="history-item"]:first-child')).toContainText('get_weather');
});
test('错误处理流程', async ({ page }) => {
// 测试错误场景
await page.goto('/tools');
await page.click('[data-testid="tool-weather"]');
// 不填写参数直接提交
await page.click('[data-testid="execute-button"]');
// 验证错误提示
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
await expect(page.locator('[data-testid="error-message"]')).toContainText('地点不能为空');
});
test('响应式设计测试', async ({ page, isMobile }) => {
if (isMobile) {
// 移动端特定测试
await expect(page.locator('[data-testid="mobile-menu"]')).toBeVisible();
// 测试侧边栏
await page.click('[data-testid="menu-toggle"]');
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
} else {
// 桌面端测试
await expect(page.locator('[data-testid="desktop-nav"]')).toBeVisible();
}
});
});
性能测试配置
javascript
// 🌟 K6性能测试脚本
// tests/performance/load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend } from 'k6/metrics';
// 自定义指标
const apiCalls = new Counter('api_calls_total');
const apiFailures = new Rate('api_failures');
const apiDuration = new Trend('api_duration');
// 测试配置
export const options = {
stages: [
{ duration: '2m', target: 10 }, // 预热:2分钟达到10用户
{ duration: '5m', target: 50 }, // 负载:5分钟保持50用户
{ duration: '2m', target: 100 }, // 峰值:2分钟达到100用户
{ duration: '5m', target: 100 }, // 峰值:5分钟保持100用户
{ duration: '2m', target: 0 }, // 降级:2分钟降到0用户
],
thresholds: {
http_req_duration: ['p(95)<2000'], // 95%的请求在2秒内完成
http_req_failed: ['rate<0.05'], // 错误率低于5%
api_duration: ['p(99)<3000'], // 99%的API调用在3秒内完成
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
export default function() {
const testScenarios = [
() => testHealthCheck(),
() => testWeatherTool(),
() => testToolsList(),
() => testAuthentication(),
];
// 随机选择测试场景
const scenario = testScenarios[Math.floor(Math.random() * testScenarios.length)];
scenario();
sleep(1); // 用户思考时间
}
function testHealthCheck() {
const response = http.get(`${BASE_URL}/health`);
check(response, {
'健康检查状态码为200': (r) => r.status === 200,
'健康检查响应时间<100ms': (r) => r.timings.duration < 100,
});
apiCalls.add(1);
if (response.status !== 200) {
apiFailures.add(1);
}
apiDuration.add(response.timings.duration);
}
function testWeatherTool() {
const cities = ['北京', '上海', '广州', '深圳', '杭州'];
const city = cities[Math.floor(Math.random() * cities.length)];
const payload = JSON.stringify({
tool: 'get_weather',
parameters: { location: city }
});
const params = {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer test_token',
},
};
const response = http.post(`${BASE_URL}/api/tools/execute`, payload, params);
check(response, {
'天气工具状态码为200': (r) => r.status === 200,
'天气工具返回结果': (r) => {
try {
const data = JSON.parse(r.body);
return data.result && data.result.temperature !== undefined;
} catch (e) {
return false;
}
},
'天气工具响应时间<2s': (r) => r.timings.duration < 2000,
});
apiCalls.add(1);
if (response.status !== 200) {
apiFailures.add(1);
}
apiDuration.add(response.timings.duration);
}
function testToolsList() {
const response = http.get(`${BASE_URL}/api/tools`);
check(response, {
'工具列表状态码为200': (r) => r.status === 200,
'工具列表包含工具': (r) => {
try {
const data = JSON.parse(r.body);
return Array.isArray(data.tools) && data.tools.length > 0;
} catch (e) {
return false;
}
},
});
apiCalls.add(1);
if (response.status !== 200) {
apiFailures.add(1);
}
apiDuration.add(response.timings.duration);
}
function testAuthentication() {
const loginPayload = JSON.stringify({
username: 'testuser',
password: 'testpass'
});
const response = http.post(`${BASE_URL}/api/auth/login`, loginPayload, {
headers: { 'Content-Type': 'application/json' },
});
check(response, {
'登录状态码为200': (r) => r.status === 200,
'登录返回token': (r) => {
try {
const data = JSON.parse(r.body);
return data.access_token !== undefined;
} catch (e) {
return false;
}
},
});
apiCalls.add(1);
if (response.status !== 200) {
apiFailures.add(1);
}
apiDuration.add(response.timings.duration);
}
// 压力测试配置
// tests/performance/stress-test.js
export const stressOptions = {
stages: [
{ duration: '1m', target: 50 }, // 快速上升到50用户
{ duration: '1m', target: 100 }, // 上升到100用户
{ duration: '1m', target: 200 }, // 上升到200用户
{ duration: '1m', target: 300 }, // 上升到300用户
{ duration: '2m', target: 400 }, // 推到极限400用户
{ duration: '1m', target: 0 }, // 快速降级
],
thresholds: {
http_req_duration: ['p(90)<5000'], // 放宽要求
http_req_failed: ['rate<0.1'], // 允许10%失败率
},
};
小结
自动化测试实战的核心要素:
🤖 自动化策略
- CI/CD集成 - 完整的流水线自动化
- 多层次测试 - 单元/集成/端到端/性能/安全测试
- 环境管理 - 开发/测试/暂存/生产环境隔离
- 质量门禁 - 代码质量、测试覆盖率、性能指标检查
- 快速反馈 - 及时发现问题并通知相关人员
💡 实践要点
- 采用分层测试策略,底层测试快速反馈,顶层测试全面覆盖
- 建立完善的Mock和测试数据管理机制
- 实施渐进式部署,降低生产环境风险
- 建立性能基准线和监控告警机制
- 重视测试环境的稳定性和可维护性
🚀 自动化哲学:自动化测试不是万能的,但没有自动化测试是万万不能的。好的自动化测试让团队更有信心去创新和迭代。
下一节:工作流设计模式 - 掌握常见的MCP应用架构模式