1. 测试环境配置与基础框架搭建
1.1 环境依赖安装
首先创建虚拟环境并安装基础依赖包:
bash
python -m venv venv
source venv/bin/activate
pip install fastapi==0.109.1 uvicorn==0.25.0 pydantic==2.6.1 pytest==7.4.4 httpx==0.27.0
版本说明:
pydantic==2.6.1
:数据验证核心库pytest==7.4.4
:测试框架主体httpx==0.27.0
:TestClient的HTTP请求实现
1.2 项目结构设计
采用分层架构保证可测试性:
bash
myapp/
├── main.py # FastAPI主入口
├── routers/ # 路由模块
│ └── items.py
├── models/ # Pydantic模型
│ └── schemas.py
└── tests/ # 测试目录
├── conftest.py # pytest全局配置
└── test_items.py
1.3 基础框架实现
在main.py
中初始化FastAPI应用:
python
from fastapi import FastAPI
from routers import items_router # 导入路由
app = FastAPI(title="My API")
# 挂载路由模块
app.include_router(
items_router,
prefix="/items",
tags=["商品管理"]
)
# 健康检查端点
@app.get("/health")
def health_check():
return {"status": "ok"}
在routers/items.py
中实现业务路由:
python
from fastapi import APIRouter, HTTPException
from models.schemas import ItemCreate, ItemResponse
router = APIRouter()
# 内存模拟数据库
fake_db = []
@router.post("/", response_model=ItemResponse)
def create_item(item: ItemCreate):
# 使用Pydantic自动验证输入数据
new_item = {"id": len(fake_db)+1, **item.dict()}
fake_db.append(new_item)
return new_item
@router.get("/{item_id}", response_model=ItemResponse)
def get_item(item_id: int):
# 使用短路逻辑替代多层if判断
item = next((i for i in fake_db if i["id"] == item_id), None)
return item or HTTPException(404, "Item not found")
在models/schemas.py
中定义数据模型:
python
from pydantic import BaseModel
class ItemCreate(BaseModel):
name: str
price: float
description: str | None = None
class ItemResponse(ItemCreate):
id: int # 返回模型扩展ID字段
2. pytest框架与FastAPI TestClient集成
2.1 测试环境配置
在tests/conftest.py
中设置全局测试装置:
python
import pytest
from fastapi.testclient import TestClient
from main import app # 导入主应用
@pytest.fixture(scope="module")
def test_client():
# 创建测试客户端实例
with TestClient(app) as client:
yield client # 供所有测试用例使用
2.2 完整测试用例实现
在tests/test_items.py
中编写端到端测试:
python
import pytest
from models.schemas import ItemCreate
def test_item_lifecycle(test_client):
"""测试商品创建、查询、异常流"""
# 创建测试数据
test_item = ItemCreate(
name="MacBook Pro",
price=12999.9,
description="M3芯片"
)
# TEST 1: 创建商品
response = test_client.post("/items/", json=test_item.dict())
assert response.status_code == 200
created_item = response.json()
assert created_item["id"] == 1
# TEST 2: 精确查询
response = test_client.get(f"/items/{created_item['id']}")
assert response.json()["name"] == "MacBook Pro"
# TEST 3: 异常路径测试
response = test_client.get("/items/999")
assert response.status_code == 404
assert response.json()["detail"] == "Item not found"
# TEST 4: 非法参数验证
bad_item = {"price": "not_number"} # 错误类型
response = test_client.post("/items/", json=bad_item)
assert response.status_code == 422 # Pydantic验证失败
2.3 异步测试支持
需要安装额外依赖:
bash
pip install pytest-asyncio==0.23.0
异步测试写法:
python
import pytest
@pytest.mark.asyncio
async def test_async_endpoint():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/async-route")
assert response.status_code == 200
3. 测试运行与报告
3.1 运行测试命令
bash
pytest -v --cov=myapp --cov-report=html tests/
关键参数说明:
--cov
:生成测试覆盖率报告-v
:显示详细测试结果
3.2 测试覆盖率优化策略
- 使用
@pytest.mark.parametrize
实现参数化测试:
python
@pytest.mark.parametrize("price, status", [
(10.5, 200), # 有效价格
(-1, 422), # 无效负数价格
("text", 422) # 错误类型
])
def test_price_validation(test_client, price, status):
response = test_client.post("/items/", json={"name": "Test", "price": price})
assert response.status_code == status
- 边界值测试:
python
# 在conftest.py中添加装置重置DB
@pytest.fixture(autouse=True)
def clean_db():
fake_db.clear() # 每个测试后清空模拟数据库
4. 课后Quiz
问题1 :当测试需要独立数据库环境时,应该使用哪种pytest fixture作用域?
A) session作用域
B) module作用域
C) function作用域 ✅
D) class作用域
解析 :function作用域(默认)会在每个测试用例执行前重新初始化fixture,确保测试隔离性。对应代码
@pytest.fixture(scope="function")
问题2 :如何测试FastAPI依赖注入的系统?
A) 直接调用依赖函数
B) 使用app.dependency_overrides
重写依赖 ✅
C) 在测试中初始化依赖对象
D) 跳过依赖测试
解析 :FastAPI提供
dependency_overrides
机制,可在测试中替换依赖实现:
pythonapp.dependency_overrides[real_dependency] = mock_dependency
问题3 :测试中出现403 Forbidden
错误,最可能的原因是?
A) 测试数据验证失败
B) 认证中间件拦截请求 ✅
C) 数据库连接错误
D) 路由路径错误
解析:安全中间件(如OAuth2)会返回403,需要在TestClient中配置认证信息:
pythontest_client.post("/secure", headers={"Authorization": "Bearer token"})
5. 常见报错解决方案
错误1:422 Unprocessable Entity
json
{
"detail": [{
"loc": ["body","price"],
"msg": "value is not a valid float",
"type": "type_error.float"
}]
}
原因 :请求体参数类型不匹配Pydantic模型
解决方案:
- 检查测试数据是否符合模型定义
- 使用模型类的
dict()
方法生成有效负载:
python
ItemCreate(name="Valid").dict() # 而非手动构造字典
错误2:500 Internal Server Error
日志显示 :AttributeError: 'TestClient' object has no attribute 'some_method'
原因 :测试代码直接调用了应用内部方法
修复方案 :
✅ 所有操作通过HTTP接口执行
python
# 正确
test_client.get("/endpoint")
# 错误
app.some_internal_method() # 绕过HTTP层
错误3:AssertionError: 404 != 200
发生场景 :路由路径配置错误
调试步骤:
- 检查
app.include_router
的prefix参数 - 打印应用路由信息:
python
print([route.path for route in app.routes])
- 确保测试路径包含完整前缀(如
/api/items
)
6. 高级测试策略
6.1 数据库事务测试
使用真实数据库时配置事务回滚:
python
# conftest.py中配置数据库会话
@pytest.fixture
def db_session(connection):
transaction = connection.begin()
session = Session(bind=connection)
yield session
session.close()
transaction.rollback() # 测试后回滚
6.2 测试覆盖率陷阱
避免被误导的覆盖率指标:
- 业务逻辑覆盖 > 行覆盖率
- 必需覆盖场景:
- 所有异常分支(HTTPException)
- 边界条件(如max_length验证)
- 安全相关路径(权限检查)