🔄 第10课:自动化测试

📚 核心概念

🎯 自动化测试:质量守门员

没有自动化测试的CI/CD就像没有刹车的跑车——速度越快越危险。自动化测试是DevOps质量保证的基石,从单元测试到端到端测试,构建完整的测试金字塔。

🔺 测试金字塔

          /\
         /  \        E2E测试(少量)
        / E2E\       - 用户流程验证
       /──────\      - 执行慢,维护成本高
      /        \
     / 集成测试  \    Integration测试(适量)
    /            \   - API/服务间交互
   /──────────────\  - 中等速度
  /                \
 /   单元测试        \  Unit测试(大量)
/____________________\ - 快速,稳定,成本低
                       - 覆盖所有逻辑分支

📊 测试覆盖率目标

测试类型覆盖率目标执行时间运行频率
单元测试80%+秒级每次提交
集成测试关键路径100%分钟级每次PR
E2E测试核心流程100%10分钟+合并到main
性能测试关键接口分钟级每日/发版前
安全测试OWASP Top10分钟级每周/发版前

⌨️ 命令实操

⌨️ 实操命令

1. 单元测试 ✅

Unit Tests
# JavaScript/Node.js
npm install --save-dev jest @types/jest
cat > sum.test.js << 'EOF'
const sum = require('./sum');

describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
  test('handles negative numbers', () => {
    expect(sum(-1, 1)).toBe(0);
  });
  test('throws on non-numeric input', () => {
    expect(() => sum('a', 1)).toThrow();
  });
});
EOF
npm test

# Python
pip install pytest pytest-cov
cat > test_calculator.py << 'EOF'
import pytest
from calculator import Calculator

@pytest.fixture
def calc():
    return Calculator()

class TestCalculator:
    def test_add(self, calc):
        assert calc.add(1, 2) == 3
    
    def test_divide_by_zero(self, calc):
        with pytest.raises(ValueError, match="除零错误"):
            calc.divide(1, 0)
    
    @pytest.mark.parametrize("a,b,expected", [
        (1, 2, 3), (-1, 1, 0), (0, 0, 0)
    ])
    def test_add_cases(self, calc, a, b, expected):
        assert calc.add(a, b) == expected
EOF
pytest --cov=calculator --cov-report=html test_calculator.py

2. 集成测试 ✅

Integration Tests
# API集成测试(Python + pytest + httpx)
pip install pytest-asyncio httpx
cat > test_api.py << 'EOF'
import pytest
from httpx import AsyncClient
from app.main import app  # FastAPI应用

@pytest.fixture
async def client():
    async with AsyncClient(app=app, base_url="http://test") as c:
        yield c

@pytest.mark.asyncio
async def test_health(client):
    response = await client.get("/health")
    assert response.status_code == 200
    assert response.json()["status"] == "ok"

@pytest.mark.asyncio
async def test_create_order(client):
    response = await client.post("/api/orders", json={
        "product_id": "P001",
        "quantity": 2
    })
    assert response.status_code == 201
    data = response.json()
    assert "order_id" in data
    assert data["status"] == "created"
EOF

# 使用Testcontainers(真实数据库)
pip install testcontainers
cat > test_with_db.py << 'EOF'
from testcontainers.postgres import PostgresContainer

def test_with_real_db():
    with PostgresContainer("postgres:16") as pg:
        engine = create_engine(pg.get_connection_url())
        # 使用真实数据库测试
        result = engine.execute("SELECT 1")
        assert result.scalar() == 1
EOF

3. 性能测试 ✅

Performance Tests
# k6性能测试脚本
cat > load-test.js << 'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const errorRate = new Rate('errors');

export const options = {
  stages: [
    { duration: '30s', target: 20 },   // 逐步加到20用户
    { duration: '1m',  target: 20 },   // 保持20用户1分钟
    { duration: '30s', target: 100 },  // 压测加到100用户
    { duration: '1m',  target: 100 },  // 保持100用户
    { duration: '30s', target: 0 },    // 逐步降为0
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],   // 95%请求<500ms
    errors: ['rate<0.05'],              // 错误率<5%
  },
};

export default function () {
  const res = http.get('https://api.example.com/products');
  
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  }) || errorRate.add(1);
  
  sleep(1);
}
EOF

# 运行
k6 run load-test.js
# 输出包含: RPS, 延迟分布, 错误率等

📐 架构图

📐 架构图:测试流水线集成

  代码提交 → CI Pipeline
               │
    ┌──────────┼──────────┐
    ▼          ▼          ▼
  Lint      Unit Test  SAST
  (ESLint)  (Jest)     (SonarQube)
    │          │          │
    └──────┬───┘──────────┘
           ▼
    Integration Test
    (pytest + Testcontainers)
           │
    ┌──────┼──────────┐
    ▼      ▼          ▼
  E2E   Perf Test   DAST
  (Cypress)(k6)    (OWASP ZAP)
    │      │          │
    └──────┴──────────┘
           ▼
    构建Docker镜像 → Push → Deploy

🔧 故障排查

🔧 故障排查

❌ 问题1:测试在CI中失败但本地通过

# 常见原因及解决:
# 1. 环境差异 → 使用Docker统一环境
# 2. 时序问题 → 增加等待/重试
# 3. 数据依赖 → 使用Testcontainers
# 4. 并发冲突 → 随机化端口/数据

# 调试CI测试失败
- name: Debug on failure
  if: failure()
  run: |
    cat test-results.xml
    docker logs $(docker ps -q) 2>&1 || true

🔺 测试金字塔

          /\
         /  \        E2E测试(少量)
        / E2E\       - 用户流程验证
       /──────\      - 执行慢,维护成本高
      /        \
     / 集成测试  \    Integration测试(适量)
    /            \   - API/服务间交互
   /──────────────\  - 中等速度
  /                \
 /   单元测试        \  Unit测试(大量)
/____________________\ - 快速,稳定

2. 集成测试 ✅

Integration Tests
# Python API集成测试
pip install pytest-asyncio httpx
cat > test_api.py << 'EOF'
import pytest
from httpx import AsyncClient
from app.main import app

@pytest.fixture
async def client():
    async with AsyncClient(app=app, base_url="http://test") as c:
        yield c

@pytest.mark.asyncio
async def test_health(client):
    response = await client.get("/health")
    assert response.status_code == 200

@pytest.mark.asyncio
async def test_create_order(client):
    response = await client.post("/api/orders", json={
        "product_id": "P001", "quantity": 2
    })
    assert response.status_code == 201
    assert "order_id" in response.json()
EOF

# 使用Testcontainers(真实数据库)
pip install testcontainers
# 在测试中启动真实PostgreSQL容器

3. 性能测试(k6) ✅

k6 Performance
cat > load-test.js << 'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m',  target: 100 },
    { duration: '30s', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
  },
};

export default function () {
  const res = http.get('https://api.example.com/products');
  check(res, {
    'status 200': (r) => r.status === 200,
    'latency < 500ms': (r) => r.timings.duration < 500,
  });
  sleep(1);
}
EOF

k6 run load-test.js

🏆 成就解锁:测试工程师!

掌握测试金字塔、单元/集成/E2E测试、性能测试——质量是DevOps的生命线。