本文导读

    FastAPI与持续集成流水线构建 1.1 什么是持续集成(CI)? 持续集成(Continuous Integration,简称CI)是一种软件开发实践:开发人员频繁将代码提交到共享仓库(如GitH

FastAPI与持续集成流水线构建

1.1 什么是持续集成(CI)?

持续集成(Continuous Integration,简称CI)是一种软件开发实践:开发人员频繁将代码提交到共享仓库(如GitHub的main分支),CI工具自动触发构建、测试流程,尽早发现代码中的错误。其核心目标是“快速反馈”——避免代码集成时出现大规模冲突,或因单个提交破坏整个项目的功能。

对于FastAPI项目而言,CI的价值在于:

  • 保证类型安全:FastAPI依赖Pydantic模型做请求验证,CI能自动检查提交的代码是否违反类型约束;
  • 避免端点失效:自动测试FastAPI的API端点(如GET /items/POST /users/),确保接口功能正常;
  • 一致性验证:确保代码在不同环境(开发、测试、生产)中的行为一致。

1.2 选择CI工具:GitHub Actions为例

目前主流的CI工具包括GitHub Actions、GitLab CI、Jenkins等。本文以GitHub Actions为例(因多数FastAPI项目托管在GitHub,配置简单且免费)。

GitHub Actions的核心概念:

  • Workflow(工作流):定义CI的触发条件和执行步骤,存储在项目根目录的.github/workflows/文件夹中;
  • Job(任务):一个Workflow可包含多个Job,如“构建代码”“运行测试”“推送镜像”;
  • Step(步骤):Job的具体执行单元,如“拉取代码”“安装依赖”。

1.3 编写FastAPI项目的CI流水线配置

以下是一个完整的FastAPI CI工作流文件.github/workflows/ci.yml),涵盖“代码拉取→依赖安装→测试→构建Docker镜像”:

name: FastAPI CI  # 工作流名称
on:  # 触发条件:Push到main分支或PR到main分支
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-and-test:  # 定义一个名为build-and-test的Job
    runs-on: ubuntu-latest  # 运行在Ubuntu最新版本的虚拟环境中
    steps:
      # 步骤1:拉取项目代码(必须第一步,否则后续步骤无代码可操作)
      - name: Checkout code
        uses: actions/checkout@v4  # 使用官方的checkout动作,拉取代码到虚拟环境

      # 步骤2:设置Python环境(匹配项目的Python版本)
      - name: Set up Python 3.11
        uses: actions/setup-python@v5  # 官方的Python环境配置动作
        with:
          python-version: "3.11"  # 指定Python版本(需与项目依赖一致)

      # 步骤3:安装项目依赖(使用requirements.txt)
      - name: Install dependencies
        run: |  # 运行Shell命令
          python -m pip install --upgrade pip  # 升级pip到最新版
          pip install -r requirements.txt  # 安装requirements.txt中的依赖

      # 步骤4:运行Pytest测试(生成JUnit格式的测试报告)
      - name: Run tests with Pytest
        run: pytest --junitxml=test-results.xml  # 运行测试并生成JUnit报告(方便后续查看)

      # 步骤5:构建Docker镜像(可选,若项目用Docker部署)
      - name: Build Docker image
        run: docker build -t my-fastapi-app:${{ github.sha }} .  # 构建镜像,标签用Git提交的SHA值(唯一标识)

配置说明

  • on字段:触发CI的场景——当代码推送到main分支,或有PR合并到main时,自动运行;
  • runs-on: ubuntu-latest:使用GitHub提供的Ubuntu虚拟环境,无需自己搭建服务器;
  • --junitxml=test-results.xml:生成JUnit格式的测试报告,方便GitHub Actions展示测试结果;
  • ${{ github.sha }}:GitHub的内置变量,代表当前提交的SHA哈希值,用于标记Docker镜像版本(避免重复)。

1.4 流水线的关键步骤解析

  1. 拉取代码(Checkout)
    使用actions/checkout@v4动作,将GitHub仓库的代码复制到CI的虚拟环境中,是所有CI工作流的基础。

  2. 设置Python环境
    FastAPI依赖特定版本的Python(如3.9+),actions/setup-python@v5会自动安装指定版本的Python,并配置pip等工具。若不设置,虚拟环境的Python版本可能与项目不兼容(如默认Python 3.8,而项目用Python 3.11),导致依赖安装失败。

  3. 安装依赖
    通过pip install -r requirements.txt安装项目所有依赖(包括FastAPI、Pydantic、Uvicorn等)。requirements.txt需包含项目的所有第三方库及其版本(可通过pip freeze > requirements.txt生成)。

  4. 运行测试
    使用pytest运行测试用例(测试文件需以test_开头,如test_main.py)。--junitxml参数生成的报告可在GitHub Actions的“Tests”标签中查看,方便快速定位失败的测试用例。

Allure测试报告可视化分析

2.1 Allure是什么?

Allure是一款开源的测试报告框架,能生成美观、互动的可视化报告(支持图表、筛选、历史对比)。与传统的JUnit报告相比,Allure的优势:

  • 结构化展示:按“功能模块→用户故事→测试用例”分层,清晰呈现测试覆盖范围;
  • 丰富的元数据:支持标记测试用例的“优先级”“标签”“附件”(如接口请求日志);
  • 跨平台兼容:支持Python、Java、JavaScript等多语言,完美适配FastAPI的Pytest测试。

2.2 FastAPI与Allure的结合方式

FastAPI的测试通常使用pytestfastapi.TestClient(模拟HTTP请求),而Allure通过pytest插件allure-pytest)集成到测试流程中。具体步骤:

  1. 安装Allure CLI和allure-pytest插件;
  2. 用Allure装饰器标记测试用例(如@allure.feature“功能模块”、@allure.story“用户故事”);
  3. 运行测试生成Allure结果文件;
  4. 启动Allure服务查看可视化报告。

2.3 配置Allure环境

2.3.1 本地开发环境配置
  1. 安装Java:Allure基于Java开发,需安装JRE 8+(如OpenJDK 11):

    • Ubuntu:sudo apt install openjdk-11-jre
    • Mac:brew install openjdk@11
    • Windows:下载OpenJDK安装包(adoptium.net/)。
  2. 安装Allure CLI: 从Allure官网下载最新版本的CLI(github.com/allure-fram… PATH=$PATH:/path/to/allure-2.24.0/bin`)。

  3. 安装pytest插件

    pip install allure-pytest==2.13.2  # 最新版本(2024年3月)
    
2.3.2 CI环境配置(GitHub Actions)

在CI中安装Allure需添加以下步骤(修改.github/workflows/ci.yml):

# 在“安装依赖”步骤后添加:
- name: Set up Java 11
  uses: actions/setup-java@v4
  with:
    java-version: "11"
    distribution: "temurin"  # 使用Eclipse Temurin JDK(稳定且常用)

- name: Install Allure CLI
  run: |
    wget https://repo1.maven.org/maven2/io/qameta/allure/allure-commandline/2.24.0/allure-commandline-2.24.0.zip
    unzip allure-commandline-2.24.0.zip
    sudo mv allure-2.24.0 /opt/allure
    sudo ln -s /opt/allure/bin/allure /usr/bin/allure  # 添加到系统PATH

- name: Run tests with Allure
  run: pytest --alluredir=allure-results  # 生成Allure结果文件(存储在allure-results目录)

- name: Upload Allure results
  uses: actions/upload-artifact@v4  # 将结果文件上传为GitHub artifact
  with:
    name: allure-results
    path: allure-results
    retention-days: 7  # 结果文件保留7天

2.4 编写带Allure标记的测试用例

以下是FastAPI的Allure测试用例示例test_main.py),覆盖GET /POST /items/端点:

from fastapi.testclient import TestClient
from main import app  # 导入FastAPI应用实例
import allure

# 初始化TestClient(模拟HTTP客户端,用于测试FastAPI端点)
client = TestClient(app)

# 定义Item模型(与main.py中的模型一致,确保测试数据符合约束)
class Item:
    name: str
    price: float
    description: str | None = None

@allure.feature("Root Endpoint")  # 功能模块:根路径端点
@allure.story("Get Root Message")  # 用户故事:获取根路径的欢迎信息
@allure.title("Test root endpoint returns 200 OK")  # 测试用例标题
@allure.severity(allure.severity_level.CRITICAL)  # 优先级: critical(核心功能)
def test_read_root():
    # 发送GET请求到根路径
    response = client.get("/")
    # 断言状态码为200
    assert response.status_code == 200
    # 断言响应内容符合预期
    assert response.json() == {"message": "Hello FastAPI"}

@allure.feature("Item Management")  # 功能模块:商品管理
@allure.story("Create New Item")  # 用户故事:创建新商品
@allure.title("Test create item with valid data")  # 测试用例标题
@allure.severity(allure.severity_level.NORMAL)  # 优先级:normal(普通功能)
def test_create_item_valid_data():
    # 构造符合Pydantic约束的请求数据(price>0)
    item_data = {
        "name": "iPhone 15",
        "price": 9999.99,
        "description": "A new smartphone"
    }
    # 发送POST请求到/items/端点(JSON格式)
    response = client.post("/items/", json=item_data)
    # 断言状态码为200
    assert response.status_code == 200
    # 断言响应内容包含提交的数据(假设id自增)
    assert response.json() == {"id": 1, **item_data}

@allure.feature("Item Management")
@allure.story("Create New Item")
@allure.title("Test create item with invalid price")  # 测试用例标题:无效价格(price≤0)
@allure.severity(allure.severity_level.MINOR)  # 优先级:minor(次要功能)
def test_create_item_invalid_price():
    # 构造无效数据(price=0,违反Pydantic的gt=0约束)
    item_data = {
        "name": "Fake Phone",
        "price": 0,
        "description": "Invalid price"
    }
    # 发送POST请求
    response = client.post("/items/", json=item_data)
    # 断言状态码为422(验证错误)
    assert response.status_code == 422

2.5 生成与查看Allure报告

  1. 本地环境生成报告
    运行测试用例并生成Allure结果文件:

    pytest --alluredir=allure-results
    

    启动Allure服务查看报告:

    allure serve allure-results
    

    浏览器会自动打开报告页面(如http://localhost:5050),展示测试结果的统计信息(通过率、失败率)和详细的用例列表。

  2. CI环境查看报告
    在GitHub Actions的“Artifacts”标签中下载allure-results压缩包,解压后在本地运行allure serve allure-results即可查看报告。

课后Quiz

问题1:

在FastAPI的CI流水线中,为什么需要设置Python环境?请结合示例说明其作用。

答案解析:

CI的虚拟环境是“干净”的(默认没有安装任何Python库),设置Python环境的作用是确保虚拟环境的Python版本与项目一致。例如:

  • 若项目用Python 3.11开发,而CI虚拟环境默认是Python 3.8,安装FastAPI时会报错(FastAPI 0.110.0要求Python ≥3.9);
  • 通过actions/setup-python@v5指定python-version: "3.11",可避免版本不兼容问题,确保依赖安装和测试正常运行。

问题2:

Allure的@allure.feature@allure.story有什么区别?请用FastAPI的“商品管理”模块举例说明。

答案解析:

  • @allure.feature:标记大的功能模块(如“商品管理”“用户管理”);
  • @allure.story:标记功能模块下的具体用户故事(如“创建商品”“删除商品”“查询商品列表”)。

举例:

@allure.feature("商品管理")  # 功能模块
@allure.story("创建商品")     # 用户故事
def test_create_item():
    # 测试用例代码

常见报错解决方案

报错1:CI中运行pytest提示“ImportError: cannot import name 'app' from 'main'”

原因

  • 测试文件(如test_main.py)与main.py不在同一目录,导致Python无法找到main模块;
  • main.py中未定义app变量(app = FastAPI())。

解决

  1. 确保测试文件与main.py在同一目录;
  2. 若测试文件在tests目录下,需在conftest.py中添加以下代码(将项目根目录加入Python路径):
    import sys
    sys.path.append(".")  # 项目根目录(包含main.py的目录)
    

预防: 保持清晰的项目结构(如tests目录存放测试文件,main.py在根目录),并在pyproject.toml中配置Pytest的根目录:

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]

报错2:Allure报告提示“Empty test suite”(无测试结果)

原因

  • 测试文件未以test_开头(如main_test.py,Pytest默认不会识别);
  • 测试函数未以test_开头(如create_item_test(),Pytest不会执行);
  • --alluredir参数指定的目录错误(如allure-results拼写错误)。

解决

  1. 修改测试文件/函数名称,确保以test_开头;
  2. 检查pytest命令的--alluredir参数,确保目录名称正确(如allure-results)。

预防: 遵循Pytest的命名规范(测试文件test_*.py、测试函数test_*),并在pyproject.toml中配置Pytest的测试文件匹配规则:

[tool.pytest.ini_options]
python_files = "test_*.py"
python_functions = "test_*"

报错3:FastAPI测试提示“422 Unprocessable Entity”

原因: 请求数据违反Pydantic模型的约束(如price字段小于等于0,或name字段为空)。

解决

  1. 检查测试用例的请求数据,确保符合Pydantic模型的所有约束;
  2. 使用response.json()查看详细的错误信息(如"msg": "Input should be greater than 0")。

示例: 若Item模型的price字段定义为price: float = Field(..., gt=0),则测试用例中的price必须大于0:

# 错误数据(price=0)
item_data = {"name": "Fake Phone", "price": 0}
# 正确数据(price=999.99)
item_data = {"name": "iPhone 15", "price": 999.99}

预防: 在编写测试用例前,仔细阅读Pydantic模型的约束条件(如gtmin_lengthregex),确保测试数据符合要求。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]