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(智元界) 原创发布