如何用 GitHub Actions 为 FastAPI 项目打造自动化测试流水线?

95 阅读9分钟

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的模块搜索路径未包含项目根目录。解决方法:

  1. 确保运行pytest时的当前目录是项目根目录(如fastapi-ci-demo/);
  2. tests目录下创建__init__.py,标识tests为Python模块;
  3. 运行pytest时添加--rootdir参数:pytest --rootdir . tests/

6. 常见报错与解决方案

在CI流水线运行中,你可能会遇到以下常见错误,提前掌握解决方案能节省大量时间。

报错1:422 Validation Error(测试或API请求中)

  • 原因:请求数据不符合Pydantic模型的验证规则(如缺少必填字段、类型错误);
  • 解决步骤
    1. 查看响应的detail字段(如response.json()["detail"]),找到错误位置(loc字段)和原因(msg字段);
    2. 修改请求数据,使其符合模型规则(如补充缺少的email字段);
  • 预防建议:编写测试用例时覆盖所有异常场景(如缺少字段、格式错误)。

报错2:pytest找不到测试用例(collected 0 items)

  • 原因:测试文件/函数的命名不符合pytest的默认规则(文件以test_开头/结尾,函数以test_开头);
  • 解决步骤
    1. 将测试文件重命名为test_api.py(而非api.py);
    2. 将测试函数重命名为test_create_user(而非create_user);
  • 预防建议:遵循pytest命名规范,避免手动指定测试文件路径。

报错3:GitHub Actions中“Install dependencies”失败

  • 原因
    1. requirements.txt中的版本号错误(如fastapi==999.0,不存在的版本);
    2. pip源无法访问(如默认源被墙);
  • 解决步骤
    1. 验证依赖版本(通过PyPI查询最新版);
    2. 更换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)。