自动化测试
测试关注接口行为
手动操作文档适合探索接口;自动化测试适合在修改代码后确认已有约定仍然成立。
好的测试不仅看成功,还看不存在的资源、错误输入、局部更新、权限不足和响应字段。无需为了凑数量逐行重复实现逻辑。
安装:
python -m pip install pytest httpx2TestClient 可以直接测试应用,不必启动独立监听端口的服务器,普通调用使用同步方法即可。官方测试说明
版本说明:当前 Starlette 的测试客户端推荐安装 httpx2。旧教程常写 httpx,在部分新版环境中仍可运行,但会出现弃用提示。本手册的异步网络请求示例仍使用 httpx,测试示例使用 TestClient,二者的导入代码不同。Starlette 官方测试客户端说明
完整示例:测试第 14 章
将第 14 章代码保存为 todo_memory.py,在同一目录保存 test_todo_memory.py:
from itertools import count
import pytest
from fastapi.testclient import TestClient
import todo_memory
@pytest.fixture
def client():
todo_memory.todos.clear()
todo_memory.id_counter = count(1)
with TestClient(todo_memory.app) as test_client:
yield test_client
todo_memory.todos.clear()
def test_create_and_read(client):
response = client.post("/todos", json={"title": "学习测试"})
assert response.status_code == 201
todo_id = response.json()["id"]
detail = client.get(f"/todos/{todo_id}")
assert detail.status_code == 200
assert detail.json()["title"] == "学习测试"
assert detail.json()["done"] is False
@pytest.mark.parametrize("payload", [
{"title": ""},
{"title": " "},
{"title": "学习", "unknown_field": 1},
])
def test_invalid_create(client, payload):
assert client.post("/todos", json=payload).status_code == 422
def test_patch_preserves_and_clears_fields(client):
created = client.post(
"/todos", json={"title": "原始标题", "description": "原始描述"}
).json()
path = f"/todos/{created['id']}"
updated = client.patch(path, json={"done": True}).json()
assert updated["title"] == "原始标题"
assert updated["description"] == "原始描述"
assert updated["done"] is True
cleared = client.patch(path, json={"description": None}).json()
assert cleared["description"] is None
assert client.patch(path, json={"title": None}).status_code == 422
def test_delete_and_missing(client):
created = client.post("/todos", json={"title": "待删除"}).json()
path = f"/todos/{created['id']}"
response = client.delete(path)
assert response.status_code == 204
assert response.content == b""
assert client.get(path).status_code == 404
def test_invalid_pagination(client):
assert client.get("/todos?limit=0").status_code == 422
assert client.get("/todos?skip=-1").status_code == 422运行:
python -m pytest test_todo_memory.py -qjson= 表示把 Python 数据编码为 JSON 请求体;response.json() 解析 JSON 响应。fixture 为每个测试准备干净数据,避免测试相互影响。
数据库测试:改用独立 MySQL 8.0 测试库
第 15 章使用 MySQL 8.0,第 26~38 章使用 Tortoise ORM。对应的完整数据库测试放在第 40 章,包含隔离测试库配置、异步客户端、fixture、增删改查、用户归属、空值更新、多对多关系、唯一约束、事务回滚与并发扣库存。
前面的内存接口测试仍可独立运行。学习数据库后,连接专用 MySQL 8.0 测试库验证实际约束与事务;测试账号只获得测试库权限。不要用正式库运行测试。
第 40 章的执行方式:
python -m pip install pytest pytest-asyncio httpx2
python -m pytest tests/test_mysql_todos.py -qHTTPX 的 ASGI transport 不会自动执行 lifespan,异步测试中需显式进入应用 lifespan;数据库连接与使用它的测试操作保持在正确的事件循环中。具体代码见第 40 章。
lifespan 测试要使用上下文
使用 with TestClient(app) as client: 会让测试进入应用生命周期。仅创建一个客户端对象,不应被当作已经完整执行 startup 和 shutdown 的证据。官方生命周期测试说明
推荐测试矩阵
| 功能 | 至少验证什么 |
|---|---|
| 创建 | 成功、必填字段缺失、非法值 |
| 查询 | 存在、不存在、分页范围、筛选 |
| PATCH | 保留未传字段、清空可空字段、拒绝非法空值 |
| 删除 | 无正文、删除后不存在 |
| 登录 | 正确密码、错误密码、未知用户 |
| Token | 缺少、篡改、过期、缺少声明 |
| 授权 | 普通用户不能执行管理员操作、不能访问他人数据 |
| 文件 | 小文件成功、超限拒绝 |
本章练习
为第 16 章补上认证测试;为内存项目补上列表筛选、空 PATCH 和 PUT 替换测试。
过关标准:能设计独立、可重复运行的测试,且测试能发现真正的行为变化。