一、 多模块集成测试实践
在大型项目中,FastAPI应用通常拆分为多个模块(如路由模块、服务层、数据层)。集成测试的重点是验证模块间的交互是否符合预期。
实现方案:
-
测试数据库隔离 :使用
pytest
的fixture
创建临时数据库,避免污染生产数据。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()
-
模拟外部依赖 :使用
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应用中,耗时操作 (如邮件发送、文件处理)和定时任务(如数据备份、报表生成)是常见需求。如果同步执行这些操作:
- 会阻塞请求处理线程
- 导致用户等待时间过长
- 降低系统整体吞吐量
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()
# 验证日志输出或状态变更
# (需在实际项目中添加检测点)
五、最佳实践指南
- 任务幂等性:确保相同任务重复执行不会产生副作用
- 异常处理:所有任务必须包含异常捕获
python
def send_email(email, message):
try:
# 发送逻辑
except Exception as e:
logger.error(f"邮件发送失败: {str(e)}")
- 资源限制:配置任务并发量防止系统过载
python
# 限制最大并发线程数
background_tasks.add_task(..., max_workers=5)
- 监控集成:添加Prometheus指标暴露
python
from prometheus_fastapi_instrumentator import Instrumentator
@app.on_event("startup")
def init_monitoring():
Instrumentator().instrument(app).expose(app)
六、课后 Quiz
-
Q: 当添加100个后台任务但线程池只有10个线程时会发生什么?
A: 任务进入队列依次执行
B: 第11个任务直接失败
C: 自动扩容线程池
D: 阻塞主线程
答案: A
解析:FastAPI使用队列管理超额任务,当线程池满时新任务排队等候 -
Q: 如何确保定时任务在服务器重启后不丢失?
A: 使用数据库持久化任务状态
B: 配置APScheduler的持久化存储
C: 每次启动重新注册任务
D: 使用外部任务队列如Celery
答案: B
解析:APScheduler支持SQLite/Redis等持久化存储:pythonscheduler = 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
定时任务消失
-
场景:修改代码后重启服务导致任务丢失
-
预防 :
- 启用持久化存储
- 添加任务前检查是否已存在
pythonif not scheduler.get_job('backup_job'): scheduler.add_job(..., id='backup_job')
7.3 报错:422 Validation Error
-
高频场景:任务参数类型错误
-
调试步骤 :
- 使用Pydantic验证输入参数
pythondef send_email(email: EmailStr, message: str): # EmailStr会自动验证邮箱格式
- 测试时强制触发验证
pythonfrom 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