当前位置: 首页 > news >正文

AI 全栈开发实战(11):CI/CD 与自动化测试——从 pytest 到 GitHub Actions

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

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

本篇回答三个问题:

  1. 怎么写 AI 项目的自动化测试?
  2. CI/CD 流水线怎么配?
  3. 部署自动化怎么做?

怎么写 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-imports

Docker 镜像构建与推送

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保证代码规范
CIGitHub Actions自动运行测试和检查
CDDocker + SSH自动构建和部署

本文是《AI 全栈开发实战——做一个真正的产品》系列的第 11 篇。


如果觉得有用,欢迎点赞 + 收藏 + 关注。这个系列从产品定义写到生产上线,全部代码开源可运行,带你从零交付一个真正的 AI 产品。

http://www.cnnetsun.cn/news/3162673.html

相关文章:

  • Codex App 26.616 新功能教程:Record Replay 录制与回放使用指南
  • AI 全栈开发实战(15):全系列总结——从零到一做一个真正的 AI 产品
  • MS10-018漏洞深度剖析:从内存破坏原理到Metasploit实战利用
  • F3闪存检测工具:3步识别扩容盘,保护你的数据安全
  • Vue Picture Swipe:如何在5分钟内为你的Vue应用添加专业图片画廊
  • 26. 【C语言】编译前的“文本大师”:预处理器指令
  • web-第7次课后作业-2
  • C语言 操作符 (按位与) | (按位或) ^ (按位异或)
  • SDC命令详解:使用source命令读取脚本
  • topics in life
  • 如何利用downr1n实现iOS设备有线降级与越狱的完整指南
  • C语言 结构体(上)
  • 跨平台macOS组件下载神器:gibMacOS完全指南
  • 深耕 XR 安卓底软开发:Framework 定制、渲染优化与系统稳定性实战
  • TVA对具身智能领域的核心技术支撑(20)
  • 不同进程的线程切换**不一定引起进程切换**,但**必然涉及进程上下文切换(即进程切换)**——这里需要明确概念辨析
  • CCB(Change Control Board,变更控制委员会)是一个由项目干系人代表组成的正式团体
  • 智慧职教自动化学习助手:让在线课程学习更高效
  • 如何高效使用Python无人机地面站:MAVProxy开源工具实践指南
  • 告别传统投屏:scrcpy如何解决Android设备控制的三大痛点
  • Qwen Code新特性来袭:终端支持语音对话
  • TVA推动物理AI的具身智能革命(9)
  • Kubernetes 系列【4】基础概念
  • 医学图像分割神器TotalSegmentator:三步掌握100+解剖结构自动识别
  • AI智能体开发指南:从核心概念到实践应用
  • 开源B站视频下载器:轻松获取高清内容的Python解决方案
  • 告别手动对齐!用UvSquares插件3分钟搞定Blender UV网格重塑
  • 百万瓦的电台,几秒钟换一个频率——从Aspidistra到中亚的发射机房,一段高功率短波的快调谐往事
  • 从零开始学AI:小白程序员必备收藏指南,快速掌握大模型实战技能
  • 收藏!小白程序员必看:揭秘AI大模型记忆管理的真相与优化方案