AI 项目的 CI/CD 怎么做?自动化测试和部署流水线搭建

代码写完了,怎么保证每次修改不会破坏已有功能?怎么自动部署到服务器?

本篇回答三个问题:
1. 怎么写 AI 项目的自动化测试?
2. CI/CD 流水线怎么配?
3. 部署自动化怎么做?

怎么写 AI 项目的自动化测试?

后端测试(pytest)

# backend/tests/test_api.py
import pytest
from httpx import ASGITransport, AsyncClient
from app.main import app


@pytest.fixture
def client():
    transport = ASGITransport(app=app)
    return AsyncClient(transport=transport, base_url="http://test")


@pytest.mark.asyncio
async def test_health_check(client):
    """健康检查接口测试。"""
    response = await client.get("/api/health")
    assert response.status_code == 200
    assert response.json()["status"] == "ok"


@pytest.mark.asyncio
async def test_register(client):
    """用户注册接口测试。"""
    import random
    email = f"test{random.randint(10000,99999)}@example.com"
    response = await client.post("/api/auth/register", json={
        "email": email,
        "password": "test123456",
        "nickname": "测试用户",
    })
    assert response.status_code == 201
    data = response.json()
    assert "access_token" in data
    assert data["user"]["email"] == email


@pytest.mark.asyncio
async def test_login_invalid_password(client):
    """错误密码登录测试。"""
    response = await client.post("/api/auth/login", json={
        "email": "nonexist@example.com",
        "password": "wrong",
    })
    assert response.status_code == 401


@pytest.mark.asyncio
async def test_create_knowledge_base(client):
    """创建知识库测试(需先登录)。"""
    # 先注册
    import random
    email = f"kb{random.randint(10000,99999)}@example.com"
    reg = await client.post("/api/auth/register", json={
        "email": email, "password": "test123456", "nickname": "test"
    })
    token = reg.json()["access_token"]

    # 创建知识库
    response = await client.post("/api/knowledge-bases",
        json={"name": "测试知识库", "description": "测试描述"},
        headers={"Authorization": f"Bearer {token}"}
    )
    assert response.status_code == 201
    assert response.json()["name"] == "测试知识库"

测试数据库隔离

测试不应该用生产数据库。用 pytest 的 fixture 隔离:

# backend/tests/conftest.py
import pytest
from app.database import engine, Base
from sqlalchemy.ext.asyncio import create_async_engine


@pytest.fixture(autouse=True)
async def setup_db():
    """每个测试用例使用独立的数据库会话。"""
    # 使用 SQLite 内存数据库进行测试
    test_engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with test_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    await test_engine.dispose()

CI/CD 流水线怎么配?

GitHub Actions 配置

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

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

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: know_test
          POSTGRES_USER: know
          POSTGRES_PASSWORD: know_test
        ports:
          - 5432:5432
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          cd backend
          pip install -r requirements.txt
          pip install pytest httpx pytest-asyncio

      - name: Run tests
        env:
          DATABASE_URL: postgresql+asyncpg://know:know_test@localhost:5432/know_test
          REDIS_URL: redis://localhost:6379/0
        run: |
          cd backend
          pytest tests/ -v --cov=app --cov-report=term-missing

代码质量检查

# 在 ci.yml 中加一个 lint job
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install ruff mypy
      - run: ruff check backend/
      - run: mypy backend/ --ignore-missing-imports

Docker 镜像构建与推送

  build:
    needs: [test, lint]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push backend
        uses: docker/build-push-action@v5
        with:
          context: ./backend
          push: true
          tags: yourdockerhub/know-backend:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push frontend
        uses: docker/build-push-action@v5
        with:
          context: ./frontend
          push: true
          tags: yourdockerhub/know-frontend:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

部署自动化怎么做?

自动部署到服务器

  deploy:
    needs: [build]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /opt/know
            docker compose pull
            docker compose up -d --force-recreate
            docker image prune -f

完整 CI/CD 流水线

提交代码 → 自动测试 → 代码检查 → 构建镜像 → 推送到仓库 → 自动部署
  git push    pytest      ruff       docker      docker push    ssh deploy

本地测试常用命令

# 运行所有测试
cd backend && pytest tests/ -v

# 运行单个测试文件
pytest tests/test_api.py -v

# 运行单个测试函数
pytest tests/test_api.py::test_register -v

# 带覆盖率报告
pytest tests/ --cov=app --cov-report=html

# 代码格式检查
ruff check app/

# 类型检查
mypy app/

总结

环节 工具 作用
单元测试 pytest + httpx 保证接口正确性
代码质量 ruff + mypy 保证代码规范
CI GitHub Actions 自动运行测试和检查
CD Docker + SSH 自动构建和部署

本文是 《AI 全栈开发实战——做一个真正的产品》 系列的第 11 篇。
本文由 Zyentor(智元界) 原创发布