1. FastAPI单元测试基础
FastAPI提供了强大的测试工具TestClient,它允许我们直接测试API接口而无需启动完整服务。TestClient的核心原理是模拟HTTP客户端请求,并直接调用FastAPI应用程序的路由处理器。
css
[用户请求]
↓
[TestClient] → [FastAPI应用路由]
↓
[模拟HTTP响应] → [断言验证]
测试环境搭建
首先需要安装测试依赖:
bash
pip install fastapi==0.109.1 pytest==7.4.3 httpx==0.25.2
基本测试代码示例:
python
from fastapi.testclient import TestClient
from main import app # 导入你的FastAPI应用实例
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
2. 路由层响应验证方法论
在FastAPI中,响应验证确保API返回数据的结构和类型完全符合预期,Pydantic模型是该功能的核心实现机制。
响应验证流程
css
[API请求]
↓
[路由处理器] → [返回数据]
↓
[Pydantic响应模型] → [数据验证]
↓
[通过:返回JSON] / [失败:422错误]
实践案例
python
from pydantic import BaseModel
from fastapi import FastAPI, status
app = FastAPI()
class UserResponse(BaseModel):
id: int
name: str
email: str
is_active: bool
@app.get("/users/{user_id}", response_model=UserResponse)
async def read_user(user_id: int):
# 模拟数据库查询
return {
"id": user_id,
"name": "John Doe",
"email": "john@example.com",
"is_active": True,
# 如果包含extra_field,Pydantic会自动过滤
}
# 测试代码
def test_user_response_validation():
response = client.get("/users/1")
assert response.status_code == status.HTTP_200_OK
# 验证响应结构
user = response.json()
assert "id" in user
assert "name" in user
assert "email" in user
assert "is_active" in user
assert isinstance(user["id"], int)
assert isinstance(user["is_active"], bool)
# 检测多余字段
assert "extra_field" not in user
3. 测试覆盖率与依赖模拟
测试覆盖率提升技巧
- 使用
pytest-cov
工具统计覆盖率:
bash
pip install pytest-cov==4.1.0
pytest --cov=app tests/
- 测试边界场景:
python
def test_invalid_user():
response = client.get("/users/9999")
assert response.status_code == status.HTTP_404_NOT_FOUND
依赖项模拟方案
使用unittest.mock
模拟外部服务:
python
from unittest.mock import patch
def test_external_service():
with patch("module.external_service") as mock_service:
mock_service.return_value = {"result": "mocked"}
response = client.get("/external-api")
assert response.json() == {"data": "mocked"}
4. 单元测试最佳实践
测试金字塔模型
matlab
▲
/ \ 端到端测试(5-10%)
/ \
------- 集成测试(15-20%)
------- 单元测试(70-80%)
测试文件结构
css
project/
├── app/
│ ├── main.py
│ ├── routers/
│ └── models.py
└── tests/
├── unit/
│ ├── test_routers.py
│ └── test_models.py
└── integration/
└── test_auth.py
高效测试案例
python
import pytest
from fastapi import HTTPException
# 参数化测试多种场景
@pytest.mark.parametrize("user_id,expected_status", [
(1, 200),
(0, 422),
(-1, 422),
(999, 404)
])
def test_user_endpoints(user_id, expected_status):
response = client.get(f"/users/{user_id}")
assert response.status_code == expected_status
# 测试异常处理
def test_exception_handling():
with pytest.raises(HTTPException) as exc_info:
# 触发异常的条件
raise HTTPException(status_code=400, detail="Bad request")
assert exc_info.value.status_code == 400
assert "Bad request" in exc_info.value.detail
5. 课后Quiz
问题1 :当Pydantic响应模型验证失败时,FastAPI会返回什么状态码?
A) 200 B) 400 C) 422 D) 500
问题2 :如何有效测试需要访问数据库的路由?
A) 每次都访问真实数据库
B) 使用内存数据库
C) 创建模拟对象(Mock)
D) 禁用该测试
答案解析:
- 正确答案 C) 422。当Pydantic模型验证失败时,FastAPI会返回422 Unprocessable Entity,表示客户端提供的响应数据格式不符合API契约。
- 最佳答案是 B) 或 C)。测试环境中推荐使用内存数据库(SQLite)或mock对象来避免对外部系统的依赖,确保测试的速度和稳定性。
6. 常见报错解决方案
422 Unprocessable Entity
原因分析:
- 响应包含未定义的额外字段
- 字段类型不匹配(如预期int但返回string)
- 缺失必需字段
解决方案:
-
检查响应模型定义:
response_model = UserResponse
-
使用Pydantic严格模式:
pythonclass StrictModel(BaseModel): class Config: extra = "forbid" # 禁止额外字段
-
运行
myapp --reload
时观察自动生成的文档验证
401 Unauthorized
预防措施:
python
# 测试中注入认证token
def test_protected_route():
response = client.get(
"/protected",
headers={"Authorization": "Bearer test_token"}
)
assert response.status_code == 200
500 Internal Server Error
调试建议:
-
在测试中启用详细日志:
pythonimport logging logging.basicConfig(level=logging.DEBUG)
-
使用中间件捕获异常:
python@app.middleware("http") async def catch_errors(request, call_next): try: return await call_next(request) except Exception as e: # 记录详细错误信息 logger.exception(e) return JSONResponse(status_code=500)