如何用FastAPI玩转多模块测试与异步任务,让代码不再“闹脾气”?

一、 多模块集成测试实践

在大型项目中,FastAPI应用通常拆分为多个模块(如路由模块、服务层、数据层)。集成测试的重点是验证模块间的交互是否符合预期。

实现方案

  1. 测试数据库隔离 :使用pytestfixture创建临时数据库,避免污染生产数据。

    python 复制代码
    # conftest.py
    import pytest
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    
    @pytest.fixture(scope="module")
    def test_db():
        # 使用SQLite内存数据库
        engine = create_engine("sqlite:///:memory:")
        SessionLocal = sessionmaker(bind=engine)
        # 创建所有表
        Base.metadata.create_all(bind=engine)
        yield SessionLocal()
        engine.dispose()
  2. 模拟外部依赖 :使用unittest.mock替换第三方服务(如API调用)。

    python 复制代码
    # test_payment.py
    from unittest.mock import patch
    
    def test_process_payment(test_db):
        # 模拟支付网关响应
        with patch("services.payment_gateway.charge") as mock_charge:
            mock_charge.return_value = {"status": "success"}
            response = client.post("/payments", json={"amount": 100})
            assert response.status_code == 200

流程图

graph LR A["启动测试"] --> B["初始化临时数据库"] B --> C["注入模拟依赖"] C --> D["执行API请求"] D --> E["验证响应&数据库状态"] E --> F["清理资源"]

二、异步任务实现

在Web应用中,耗时操作 (如邮件发送、文件处理)和定时任务(如数据备份、报表生成)是常见需求。如果同步执行这些操作:

  1. 会阻塞请求处理线程
  2. 导致用户等待时间过长
  3. 降低系统整体吞吐量

FastAPI通过异步架构提供了两种解决方案:

graph LR A[客户端请求] --> B{FastAPI路由} B -->|即时返回| C[同步响应] B -->|后台执行| D[异步任务] B -->|定时触发| E[定时任务]

2.1 核心组件:BackgroundTasks

使用BackgroundTasks将耗时操作放入后台执行:

python 复制代码
from fastapi import BackgroundTasks, FastAPI
from pydantic import BaseModel

app = FastAPI()


class UserRequest(BaseModel):
    email: str
    message: str


def send_email(email: str, message: str):
    # 模拟邮件发送耗时操作
    print(f"Sending email to {email}: {message}")
    # 实际项目中可使用smtplib或SendGrid库


@app.post("/notify")
async def notify_user(
        request: UserRequest,
        background_tasks: BackgroundTasks
):
    # 添加后台任务(非阻塞)
    background_tasks.add_task(
        send_email,
        request.email,
        request.message
    )
    return {"status": "Notification queued"}

2.2 实现原理

  • 异步调度器:FastAPI内置线程池管理后台任务
  • 任务隔离:每个任务在独立线程中执行
  • 自动清理:任务完成后释放资源

三、定时任务实现

3.1 使用APScheduler

APScheduler提供多种触发器类型:

python 复制代码
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime

# 创建调度器实例
scheduler = BackgroundScheduler()


# 定时任务示例:每60秒执行
@scheduler.scheduled_job('interval', seconds=60)
def database_backup():
    print(f"{datetime.now()}: Database backup started")
    # 实际备份逻辑


# 在FastAPI启动时初始化
@app.on_event("startup")
def init_scheduler():
    scheduler.start()


# 关闭时清理
@app.on_event("shutdown")
def shutdown_scheduler():
    scheduler.shutdown()

3.2 定时规则配置

触发类型 代码示例 说明
固定间隔 seconds=30 每30秒执行一次
特定时间 hour=3, minute=30 每天03:30执行
Cron表达式 cron='0 12 * * 1-5' 工作日中午12点执行
一次性任务 trigger='date' 指定具体时间执行

四、集成验证与测试

4.1 异步任务测试策略

python 复制代码
from fastapi.testclient import TestClient
from unittest.mock import patch

client = TestClient(app)


def test_notify_user():
    # 模拟后台任务函数
    with patch("main.send_email") as mock_send:
        # 发送请求
        response = client.post(
            "/notify",
            json={"email": "test@ex.com", "message": "Hello"}
        )

        # 验证即时响应
        assert response.status_code == 200
        assert response.json() == {"status": "Notification queued"}

        # 验证任务被调用
        mock_send.assert_called_once_with("test@ex.com", "Hello")

4.2 定时任务验证技巧

python 复制代码
# 手动触发任务进行验证
def test_scheduler_job():
    # 直接调用定时任务函数
    database_backup()

    # 验证日志输出或状态变更
    # (需在实际项目中添加检测点)

五、最佳实践指南

  1. 任务幂等性:确保相同任务重复执行不会产生副作用
  2. 异常处理:所有任务必须包含异常捕获
python 复制代码
def send_email(email, message):
    try:
    # 发送逻辑
    except Exception as e:
        logger.error(f"邮件发送失败: {str(e)}")
  1. 资源限制:配置任务并发量防止系统过载
python 复制代码
# 限制最大并发线程数
background_tasks.add_task(..., max_workers=5)
  1. 监控集成:添加Prometheus指标暴露
python 复制代码
from prometheus_fastapi_instrumentator import Instrumentator


@app.on_event("startup")
def init_monitoring():
    Instrumentator().instrument(app).expose(app)

六、课后 Quiz

  1. Q: 当添加100个后台任务但线程池只有10个线程时会发生什么?

    A: 任务进入队列依次执行

    B: 第11个任务直接失败

    C: 自动扩容线程池

    D: 阻塞主线程

    答案: A
    解析:FastAPI使用队列管理超额任务,当线程池满时新任务排队等候

  2. Q: 如何确保定时任务在服务器重启后不丢失?

    A: 使用数据库持久化任务状态

    B: 配置APScheduler的持久化存储

    C: 每次启动重新注册任务

    D: 使用外部任务队列如Celery

    答案: B
    解析:APScheduler支持SQLite/Redis等持久化存储:

    python 复制代码
    scheduler = BackgroundScheduler(
        jobstores={'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')}
    )

七、常见报错解决方案

7.1 报错:RuntimeError: No active scheduler

  • 原因:在路由中直接访问未初始化的scheduler
  • 修复:通过依赖注入获取实例
python 复制代码
def get_scheduler():
    return scheduler


@app.get("/jobs")
def list_jobs(sched: BackgroundScheduler = Depends(get_scheduler)):
    return [str(job) for job in sched.get_jobs()]

7.2 报错:JobNotFound 定时任务消失

  • 场景:修改代码后重启服务导致任务丢失

  • 预防

    1. 启用持久化存储
    2. 添加任务前检查是否已存在
    python 复制代码
    if not scheduler.get_job('backup_job'):
        scheduler.add_job(..., id='backup_job')

7.3 报错:422 Validation Error

  • 高频场景:任务参数类型错误

  • 调试步骤

    1. 使用Pydantic验证输入参数
    python 复制代码
    def send_email(email: EmailStr, message: str):
        # EmailStr会自动验证邮箱格式
    1. 测试时强制触发验证
    python 复制代码
    from pydantic import ValidationError
    try:
        UserRequest(email="invalid", message="test")
    except ValidationError as e:
        print(e.json())

第三方依赖清单

requirements.txt中声明:

ini 复制代码
fastapi==0.104.0
pydantic==2.6.1
apscheduler==3.10.1
pytest==7.4.0
requests==2.31.0
prometheus-fastapi-instrumentator==6.1.0
相关推荐
云起SAAS2 小时前
贪吃蛇鱼小游戏抖音快手微信小程序看广告流量主开源
ai编程·贪吃蛇
考虑考虑3 小时前
Postgerssql格式化时间
数据库·后端·postgresql
Chan163 小时前
【智能协同云图库】基于统一接口架构构建多维度分析功能、结合 ECharts 可视化与权限校验实现用户 / 管理员图库统计、通过 SQL 优化与流式处理提升数据
java·spring boot·后端·sql·spring·intellij-idea·echarts
库库林_沙琪马3 小时前
REST接口幂等设计深度解析
spring boot·后端
IT_陈寒3 小时前
Redis性能提升50%的7个关键优化策略,90%开发者都不知道第5点!
前端·人工智能·后端
智商偏低3 小时前
ASP.NET Core 身份验证概述
后端·asp.net
冷冷的菜哥3 小时前
ASP.NET Core使用MailKit发送邮件
后端·c#·asp.net·发送邮件·mailkit
canonical_entropy4 小时前
XDef:一种面向演化的元模型及其构造哲学
后端
小林coding4 小时前
再也不怕面试了!程序员 AI 面试练习神器终于上线了
前端·后端·面试