1. 持续集成与GitHub Actions基础
1.1 什么是持续集成(CI)?
持续集成(Continuous Integration,简称CI)是一种自动化开发实践:每当开发者向代码仓库推送(Push)或提交合并请求(Pull Request)时,系统会自动运行一系列预先定义的流程(如安装依赖、运行测试、检查代码风格),快速验证代码变更的正确性。
对于FastAPI项目,CI的核心价值是:
- 避免“集成地狱”:尽早发现代码中的bug(比如API端点逻辑错误、依赖冲突);
- 保证代码质量:强制遵循团队代码规范(如PEP 8);
- 加速协作:合并请求时自动验证,减少人工review的负担。
1.2 GitHub Actions 核心概念快速理解
GitHub Actions是GitHub内置的自动化工具,用于实现CI/CD流程。你可以把它想象成一个“自动化机器人”,根据你写的“指令清单”(工作流文件)执行任务。以下是几个关键概念:
- 工作流(Workflow):一个完整的自动化流程(如“运行FastAPI测试”),用YAML文件定义,存放在项目根目录的
.github/workflows/下; - 事件(Event):触发工作流的“开关”(如Push代码、提交PR);
- 作业(Job):工作流中的独立任务(如“安装依赖”“运行测试”),默认并行执行;
- 步骤(Step):作业中的具体操作(如“拉取代码”“运行pytest”),顺序执行;
- 动作(Action):可复用的“代码块”(如
actions/checkout@v4用于拉取代码),避免重复造轮子。
2. FastAPI项目初始化与测试准备
在搭建CI流水线前,我们需要先准备一个标准化的FastAPI项目结构,并编写可自动化的测试用例。
2.1 标准项目结构设计
一个清晰的项目结构能让CI流程更易维护,推荐结构如下:
fastapi-ci-demo/ # 项目根目录
├── app/ # 应用核心代码目录
│ ├── __init__.py # 标识app为Python模块
│ └── main.py # FastAPI主程序
├── tests/ # 测试用例目录
│ ├── __init__.py # 标识tests为Python模块
│ └── test_api.py # API测试用例
├── requirements.txt # 依赖清单(或用pyproject.toml+Poetry)
└── .flake8 # flake8代码风格配置文件
2.2 编写FastAPI核心代码
我们以“用户管理API”为例,编写app/main.py:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr # 导入Pydantic类型
# 1. 初始化FastAPI应用
app = FastAPI(title="用户管理API", version="1.0")
# 2. 定义数据模型(Pydantic v2)
class UserCreate(BaseModel):
name: str # 用户名:必填,字符串
email: EmailStr # 邮箱:必填,符合邮箱格式(如xxx@example.com)
age: int | None = None # 年龄:可选,整数
# 3. 模拟数据库(实际项目用SQLAlchemy/Redis)
users_db = []
# 4. 定义API端点
@app.post("/users/", response_model=UserCreate)
def create_user(user: UserCreate):
"""创建用户:接收UserCreate模型数据,存入模拟数据库"""
users_db.append(user.model_dump()) # 将Pydantic模型转为字典存库
return user # 返回创建的用户数据(自动序列化为JSON)
关键说明:
- 使用Pydantic v2的
BaseModel定义数据模型,自动完成请求数据的验证; response_model指定端点的返回格式,确保返回数据符合UserCreate规则;- 模拟数据库
users_db用于快速测试,实际项目需替换为真实数据库(如PostgreSQL)。
2.3 用pytest编写测试用例
测试是CI的核心,我们用pytest+FastAPI TestClient编写测试用例(tests/test_api.py):
from fastapi.testclient import TestClient # 导入TestClient用于模拟HTTP请求
from app.main import app # 导入FastAPI应用实例
# 创建TestClient实例(相当于“模拟浏览器”)
client = TestClient(app)
def test_create_user_success():
"""测试:成功创建用户(预期200 OK)"""
test_data = {
"name": "张三",
"email": "zhangsan@example.com",
"age": 28
}
# 发送POST请求到/users/端点,用json参数传递JSON数据
response = client.post("/users/", json=test_data)
# 断言:状态码是200,返回数据与测试数据一致
assert response.status_code == 200
assert response.json() == test_data
def test_create_user_missing_email():
"""测试:缺少必填字段email(预期422验证错误)"""
test_data = {
"name": "李四",
"age": 30
}
response = client.post("/users/", json=test_data)
# 断言:状态码是422,错误信息包含“email”字段
assert response.status_code == 422
assert "email" in response.json()["detail"][0]["loc"]
def test_create_user_invalid_email():
"""测试:邮箱格式无效(预期422验证错误)"""
test_data = {
"name": "王五",
"email": "wangwu.example.com", # 缺少@符号
"age": 25
}
response = client.post("/users/", json=test_data)
assert response.status_code == 422
assert "email" in response.json()["detail"][0]["loc"]
关键说明:
TestClient能模拟真实HTTP请求,无需启动Uvicorn服务器;- 测试用例覆盖了正常场景(成功创建)和异常场景(缺少字段、格式错误),确保API的鲁棒性;
- 使用
assert语句验证结果,pytest会自动识别并运行这些用例。
2.3 依赖清单与版本管理
在requirements.txt中列出项目依赖(需使用最新稳定版,可通过PyPI查询):
# FastAPI核心依赖
fastapi==0.104.1
uvicorn==0.24.0.post1 # ASGI服务器
# 测试依赖
pytest==7.4.3 # 测试框架
requests==2.31.0 # HTTP请求库(TestClient依赖)
# 代码风格检查
flake8==6.1.0 # 代码风格检查工具
# Pydantic
pydantic==2.5.2 # Pydantic v2最新版
3. GitHub Actions自动化测试流水线搭建
现在,我们编写工作流文件(.github/workflows/ci.yml),告诉GitHub Actions要做什么。
3.1 工作流文件基础结构
一个完整的CI工作流文件包含以下部分:
name: FastAPI CI # 工作流名称(显示在GitHub Actions页面)
on: # 触发条件:Push或PR到main分支时运行
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs: # 作业列表:一个工作流可包含多个作业
build: # 作业名称:“构建与测试”
runs-on: ubuntu-latest # 运行环境:Ubuntu最新版虚拟机
steps: # 作业步骤:顺序执行
# 步骤1:拉取代码到虚拟机
- name: Checkout code
uses: actions/checkout@v4 # 官方动作:拉取代码
# 步骤2:设置Python环境
- name: Set up Python 3.11
uses: actions/setup-python@v5 # 官方动作:安装Python
with:
python-version: "3.11" # 指定Python版本(FastAPI支持3.8+)
cache: "pip" # 缓存pip依赖,加速后续运行
# 步骤3:安装项目依赖
- name: Install dependencies
run: | # 运行命令行指令
python -m pip install --upgrade pip # 升级pip
pip install -r requirements.txt # 安装依赖
# 步骤4:运行pytest测试
- name: Run tests
run: pytest tests/ -v # 运行tests目录下的所有测试,-v显示详细输出
# 步骤5:检查代码风格(flake8)
- name: Check code style
run: flake8 app/ tests/ # 检查app和tests目录下的代码
3.2 配置说明与扩展
- 触发条件:
on.push.branches: [ "main" ]表示只有Push到main分支才触发;若想让develop分支也触发,可添加"develop"; - Python版本:
setup-python动作支持多个Python版本(如3.10、3.11),可添加matrix参数实现多版本测试(见扩展部分); - 缓存依赖:
cache: "pip"会缓存~/.cache/pip目录,避免每次重新下载依赖,大幅加快运行速度; - 代码风格检查:
flake8会按照.flake8配置文件的规则检查代码,示例.flake8配置:[flake8] max-line-length = 120 # 允许最长行120字符(默认79) exclude = .git,__pycache__,venv/ # 排除不需要检查的目录
3.3 扩展:多Python版本测试
若想验证FastAPI在多个Python版本下的兼容性,可添加matrix参数:
jobs:
build:
runs-on: ubuntu-latest
strategy: # 策略:多版本并行测试
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"] # 测试4个Python版本
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} # 动态使用Python版本
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
# 后续步骤与之前一致...
4. 流水线运行与结果验证
当你将代码Push到GitHub的main分支(或提交PR到main),GitHub Actions会自动触发工作流。你可以在GitHub仓库的Actions标签页中查看运行结果:
4.1 查看运行日志
点击具体的工作流运行记录(如“Push to main by Alice”),可以看到每个步骤的详细日志:
- 若所有步骤显示绿色的“√”,说明流水线运行成功;
- 若某步骤显示红色的“×”,说明该步骤失败(如pytest测试不通过),点击该步骤可查看错误详情。
4.2 失败场景分析与修复
以“pytest测试失败”为例,假设test_create_user_missing_email用例失败,日志显示:
AssertionError: assert 200 == 422
这说明缺少email字段时,API返回了200而不是预期的422。原因可能是UserCreate模型中的email字段被误设为可选:
# 错误写法(email可选)
class UserCreate(BaseModel):
email: EmailStr | None = None # 错误:| None = None让email可选
修复方法:去掉| None = None,让email成为必填字段:
# 正确写法(email必填)
class UserCreate(BaseModel):
email: EmailStr # 正确:无默认值,必填
修改后重新Push代码,CI流水线会再次运行,测试用例将恢复正常。
5. 课后Quiz:巩固你的CI知识
问题1
如何修改工作流配置,让流水线在Push到develop分支或提交PR到main分支时触发? 答案:
on:
push:
branches: [ "main", "develop" ] # Push到main或develop触发
pull_request:
branches: [ "main" ] # PR到main触发
问题2
FastAPI中用TestClient测试POST请求时,为什么推荐用json参数而不是data参数?
答案:
json参数会自动将Python字典转为JSON字符串,并设置请求头Content-Type: application/json(FastAPI默认接收JSON);而data参数会将数据作为表单提交(Content-Type: application/x-www-form-urlencoded),不符合API的预期格式。
问题3
若pytest运行时提示“ModuleNotFoundError: No module named 'app'”,可能的原因是什么? 答案: Python的模块搜索路径未包含项目根目录。解决方法:
- 确保运行pytest时的当前目录是项目根目录(如
fastapi-ci-demo/); - 在
tests目录下创建__init__.py,标识tests为Python模块; - 运行pytest时添加
--rootdir参数:pytest --rootdir . tests/。
6. 常见报错与解决方案
在CI流水线运行中,你可能会遇到以下常见错误,提前掌握解决方案能节省大量时间。
报错1:422 Validation Error(测试或API请求中)
- 原因:请求数据不符合Pydantic模型的验证规则(如缺少必填字段、类型错误);
- 解决步骤:
- 查看响应的
detail字段(如response.json()["detail"]),找到错误位置(loc字段)和原因(msg字段); - 修改请求数据,使其符合模型规则(如补充缺少的
email字段);
- 查看响应的
- 预防建议:编写测试用例时覆盖所有异常场景(如缺少字段、格式错误)。
报错2:pytest找不到测试用例(collected 0 items)
- 原因:测试文件/函数的命名不符合pytest的默认规则(文件以
test_开头/结尾,函数以test_开头); - 解决步骤:
- 将测试文件重命名为
test_api.py(而非api.py); - 将测试函数重命名为
test_create_user(而非create_user);
- 将测试文件重命名为
- 预防建议:遵循pytest命名规范,避免手动指定测试文件路径。
报错3:GitHub Actions中“Install dependencies”失败
- 原因:
requirements.txt中的版本号错误(如fastapi==999.0,不存在的版本);- pip源无法访问(如默认源被墙);
- 解决步骤:
- 验证依赖版本(通过PyPI查询最新版);
- 更换pip源(如使用阿里云源):
- name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
CI的核心价值是“提前发现问题”,它能让你在代码合并前就消灭bug,大幅提升项目的稳定性和开发效率。
下一步,你可以尝试扩展流水线功能,比如:
- 自动部署FastAPI到云服务器(如AWS EC2、Vercel);
- 生成测试覆盖率报告(用
pytest-cov); - 发送Slack/钉钉通知(用
act1ons/slack@v3)。