FastAPI测试实战四:pytest与TestClient从入门到可回归
到第三篇为止,我们已经有了:分层架构数据库接入JWT/OAuth2鉴权现在项目最容易出现的风险是:你每次改一处功能,都不确定会不会把别的接口弄坏这就是为什么第四步必须是测试体系建设本文目标:1. 用pytest与TestClient建立FastAPI的主流程测试2. 让CRUD、鉴权、异常语义都可回归3. 把测试接入CI,做到提交即验证。
到第三篇为止,我们已经有了:
分层架构
数据库接入
JWT/OAuth2鉴权
现在项目最容易出现的风险是:
你每次改一处功能,都不确定会不会把别的接口弄坏
这就是为什么第四步必须是测试体系建设
本文目标:
1. 用pytest与TestClient建立FastAPI的主流程测试
2. 让CRUD、鉴权、异常语义都可回归
3. 把测试接入CI,做到提交即验证
一、测试要测什么先划边界
建议优先级:
1. 接口主流程:成功路径200/201
2. 业务规则:冲突与非法输入400/409/422
3. 资源边界:不存在资源404
4. 鉴权边界:未登录/权限不足401/403
第一阶段就别追求100% 覆盖率,先保证关键路径可回归
二、依赖安装与基础约定
python -m pip install pytest pytest-cov httpx
约定建议:
- 测试目录:tests/
- 文件命名:test_*.py
- 函数命名:test_*
三、最小可用测试骨架
tests/conftest.py示意:
import pytest
from fastapi.testclient import TestClient
from app.main import app
@pytest.fixture
def client():
return TestClient(app)
第一条烟雾测试:
tests/test_health.py
def test_health(client):
resp = client.get("/health")
assert resp.status_code == 200
payload = resp.json()
assert payload["success"] is True
assert payload["data"]["status"] in ["ok", "健康"]
四、CRUD测试:按业务链路写,不按函数写
tests/test_users.py示意:
def test_user_crud_flow(client):
# 创建
create_resp = client.post("/users", json={"name": "Alice", "email": "alice@example.com"})
assert create_resp.status_code == 201
user_id = create_resp.json()["data"]["id"]
# 查询
get_resp = client.get(f"/users/{user_id}")
assert get_resp.status_code == 200
assert get_resp.json()["data"]["email"] == "alice@example.com"
# 更新
update_resp = client.put(f"/users/{user_id}", json={"name": "Alice2"})
assert update_resp.status_code == 200
assert update_resp.json()["data"]["name"] == "Alice2"
# 删除
delete_resp = client.delete(f"/users/{user_id}")
assert delete_resp.status_code == 200
为什么这么写:
贴近真实用户行为
一条测试覆盖多段关键链路
五、异常语义测试:这是联调稳定性的关键
建议至少覆盖:
1. 参数错误 -> 422
2. 资源不存在 -> 404
3. 业务冲突邮箱重复-> 400/409
示例:
def test_create_user_validation_error(client):
resp = client.post("/users", json={"name": "a", "email": "not-email"})
assert resp.status_code == 422
assert resp.json()["success"] is False
def test_get_not_found(client):
resp = client.get("/users/99999")
assert resp.status_code == 404
assert resp.json()["error"]["code"] == "HTTP_404"
def test_duplicate_email(client):
client.post("/users", json={"name": "A", "email": "dup@example.com"})
resp = client.post("/users", json={"name": "B", "email": "dup@example.com"})
assert resp.status_code in [400, 409]
assert resp.json()["success"] is False
六、鉴权测试:401/403必测
你应该至少有三类鉴权测试:
1. 未带token访问受保护接口 -> 401
2. 普通用户访问管理员接口 -> 403
3. 有效token访问 -> 200
示意:
def test_me_unauthorized(client):
resp = client.get("/me")
assert resp.status_code == 401
def test_admin_forbidden(client, user_token_headers):
resp = client.delete("/admin/users/1", headers=user_token_headers)
assert resp.status_code == 403
def test_me_authorized(client, user_token_headers):
resp = client.get("/me", headers=user_token_headers)
assert resp.status_code == 200
七、数据库测试策略:隔离、可重复、可回滚
常见做法:
测试环境使用独立数据库或测试库
每个测试前后清理数据
通过依赖覆盖override dependency注入测试Session
FastAPI常用依赖覆盖:
app.dependency_overrides[get_db] = get_test_db
完成后记得清理:
app.dependency_overrides.clear()
八、异步接口测试如果你用了async
对于异步场景,可用httpx.AsyncClient:
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_async_endpoint(app):
async with AsyncClient(app=app, base_url="http://test") as ac:
resp = await ac.get("/health")
assert resp.status_code == 200
什么时候必须上异步测试:
你有大量async route
你要测试并发行为、超时行为、取消行为
九、覆盖率不是目的,但要有底线
建议先做到:
核心业务路径覆盖
鉴权边界覆盖
异常分支覆盖
运行:
pytest -q --cov=app --cov-report=term-missing
可设阶段目标:
先60%,再75%,再按模块逐步提升
十、把测试接进CI提交即回归
如果你用GitHub Actions,最小工作流示意:
name: test
on: [push, pull_request]
jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: pip install pytest pytest-cov httpx
- run: pytest -q
收益:
坏提交会被第一时间拦截
团队协作成本下降
十一、常见坑与规避
1. 测试共享数据导致串扰
解决:每个测试独立数据准备与清理
2. 只测成功路径,不测失败路径
解决:422/404/401/403/业务冲突必须覆盖
3. 只在本地跑,不接CI
解决:提交自动跑测试,避免我本地是好的
4. 对响应结构断言太弱
解决:不仅断状态码,也断success/error/code/data
十二、本篇小结
这篇的核心不是会写pytest语法,而是把质量关口前移:
每次改动都可验证
异常语义可回归
鉴权边界可证明
下一篇是系列收官:
我们会做FastAPI的性能与部署优化,覆盖Uvicorn/Gunicorn、多环境配置、监控与日志治理
更多推荐
所有评论(0)