AI 全栈开发实战(11):CI/CD 与自动化测试——从 pytest 到 GitHub Actions
AI 项目的 CI/CD 怎么做?自动化测试和部署流水线搭建
代码写完了,怎么保证每次修改不会破坏已有功能?怎么自动部署到服务器?
本篇回答三个问题:
- 怎么写 AI 项目的自动化测试?
- CI/CD 流水线怎么配?
- 部署自动化怎么做?
怎么写 AI 项目的自动化测试?
后端测试(pytest)
# backend/tests/test_api.pyimportpytestfromhttpximportASGITransport,AsyncClientfromapp.mainimportapp@pytest.fixturedefclient():transport=ASGITransport(app=app)returnAsyncClient(transport=transport,base_url="http://test")@pytest.mark.asyncioasyncdeftest_health_check(client):"""健康检查接口测试。"""response=awaitclient.get("/api/health")assertresponse.status_code==200assertresponse.json()["status"]=="ok"@pytest.mark.asyncioasyncdeftest_register(client):"""用户注册接口测试。"""importrandom email=f"test{random.randint(10000,99999)}@example.com"response=awaitclient.post("/api/auth/register",json={"email":email,"password":"test123456","nickname":"测试用户",})assertresponse.status_code==201data=response.json()assert"access_token"indataassertdata["user"]["email"]==email@pytest.mark.asyncioasyncdeftest_login_invalid_password(client):"""错误密码登录测试。"""response=awaitclient.post("/api/auth/login",json={"email":"nonexist@example.com","password":"wrong",})assertresponse.status_code==401@pytest.mark.asyncioasyncdeftest_create_knowledge_base(client):"""创建知识库测试(需先登录)。"""# 先注册importrandom email=f"kb{random.randint(10000,99999)}@example.com"reg=awaitclient.post("/api/auth/register",json={"email":email,"password":"test123456","nickname":"test"})token=reg.json()["access_token"]# 创建知识库response=awaitclient.post("/api/knowledge-bases",json={"name":"测试知识库","description":"测试描述"},headers={"Authorization":f"Bearer{token}"})assertresponse.status_code==201assertresponse.json()["name"]=="测试知识库"测试数据库隔离
测试不应该用生产数据库。用 pytest 的 fixture 隔离:
# backend/tests/conftest.pyimportpytestfromapp.databaseimportengine,Basefromsqlalchemy.ext.asyncioimportcreate_async_engine@pytest.fixture(autouse=True)asyncdefsetup_db():"""每个测试用例使用独立的数据库会话。"""# 使用 SQLite 内存数据库进行测试test_engine=create_async_engine("sqlite+aiosqlite:///:memory:")asyncwithtest_engine.begin()asconn:awaitconn.run_sync(Base.metadata.create_all)yieldawaittest_engine.dispose()CI/CD 流水线怎么配?
GitHub Actions 配置
# .github/workflows/ci.ymlname:CIon:push:branches:[main,develop]pull_request:branches:[main]jobs:test:runs-on:ubuntu-latestservices:postgres:image:postgres:16-alpineenv:POSTGRES_DB:know_testPOSTGRES_USER:knowPOSTGRES_PASSWORD:know_testports:-5432:5432options:--health-cmd pg_isready--health-interval 10s--health-timeout 5s--health-retries 5redis:image:redis:7-alpineports:-6379:6379options:--health-cmd "redis-cli ping"--health-interval 10s--health-timeout 5s--health-retries 5steps:-uses:actions/checkout@v4-name:Set up Pythonuses:actions/setup-python@v5with:python-version:"3.11"-name:Install dependenciesrun:|cd backend pip install -r requirements.txt pip install pytest httpx pytest-asyncio-name:Run testsenv:DATABASE_URL:postgresql+asyncpg://know:know_test@localhost:5432/know_testREDIS_URL:redis://localhost:6379/0run:|cd backend pytest tests/ -v --cov=app --cov-report=term-missing代码质量检查
# 在 ci.yml 中加一个 lint joblint:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:python-version:"3.11"-run:pip install ruff mypy-run:ruff check backend/-run:mypy backend/--ignore-missing-importsDocker 镜像构建与推送
build:needs:[test,lint]runs-on:ubuntu-latestif:github.ref == 'refs/heads/main'steps:-uses:actions/checkout@v4-name:Set up Docker Buildxuses:docker/setup-buildx-action@v3-name:Log in to Docker Hubuses:docker/login-action@v3with:username:${{secrets.DOCKER_USERNAME}}password:${{secrets.DOCKER_PASSWORD}}-name:Build and push backenduses:docker/build-push-action@v5with:context:./backendpush:truetags:yourdockerhub/know-backend:latestcache-from:type=ghacache-to:type=gha,mode=max-name:Build and push frontenduses:docker/build-push-action@v5with:context:./frontendpush:truetags:yourdockerhub/know-frontend:latestcache-from:type=ghacache-to:type=gha,mode=max部署自动化怎么做?
自动部署到服务器
deploy:needs:[build]runs-on:ubuntu-latestif:github.ref == 'refs/heads/main'steps:-name:Deploy via SSHuses:appleboy/ssh-action@v1.0.3with: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本地测试常用命令
# 运行所有测试cdbackend&&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 篇。
如果觉得有用,欢迎点赞 + 收藏 + 关注。这个系列从产品定义写到生产上线,全部代码开源可运行,带你从零交付一个真正的 AI 产品。
