用 pytest 测试 flask 需启用 testing=true 并通过 fixture 提供 test_client;登录走真实路由,数据库用内存 sqlite 并在 fixture 中建表/清表;避免脱离应用上下文调用 current_app 或 db 操作。

怎么用 pytest 启动 Flask 测试客户端
直接用 pytest 跑 Flask 测试,核心是让 Flask 应用进入测试模式,并拿到可发请求的 test_client。Flask 自带的 app.test_client() 是轻量级 WSGI 客户端,不启动真实服务器,适合单元测试。
关键点:必须设置 app.config['TESTING'] = True,否则部分行为(比如错误不会抛出异常、JSON 响应不自动解析)会和预期不符。
- 在测试文件开头创建应用实例并启用测试模式:
import pytest from myapp import create_app @pytest.fixture def client(): app = create_app() app.config['TESTING'] = True with app.test_client() as client: yield client - 不手动调用
app.run()或flask run—— pytest 测试里不需要运行服务进程 - 如果用工厂函数(
create_app()),确保它支持传入配置覆盖,方便在测试中注入TESTING=True
如何模拟登录或保持 session 状态
Flask 的 test_client 默认不持久化 session,每次请求都是干净上下文。要测登录后行为,得手动设置 session 或走完整登录流程。
推荐走真实登录路由,而不是直接写 session 字典 —— 后者绕过 Flask-Login 等扩展的校验逻辑,容易掩盖权限控制缺陷。
- 先发 POST 到登录接口,带上用户名密码:
response = client.post('/login', data={'username': 'test', 'password': '123'}) - 检查响应状态码和重定向目标,确认登录成功:
assert response.status_code == 302,且response.headers.get('Location') == '/' - 后续请求会自动携带 cookie,无需额外操作 ——
test_client内部维护了会话上下文 - 若需清理登录态,用
with client.session_transaction() as sess:手动清空sess.clear()
怎么测试数据库交互(比如 SQLAlchemy)
测试数据库最怕污染真实数据或依赖外部环境。正确做法是为测试单独配一个内存数据库(如 SQLite in-memory)或临时文件数据库,并在每个测试前后重置表结构。
不要复用开发/生产数据库 URL;也不要在测试里用 db.create_all() 一次建表然后反复跑 —— 并发执行时会冲突,且状态残留难排查。
- 用
sqlite:///:memory:最快,但注意它不支持某些外键约束(需显式开启):app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
- 在 fixture 中初始化 DB:
@pytest.fixture def client(): app = create_app() app.config['TESTING'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' with app.app_context(): db.create_all() yield app.test_client() db.drop_all() - 如果业务逻辑重度依赖事务隔离,考虑用临时磁盘文件(
sqlite:///test.db)并加os.unlink清理,避免内存 DB 的局限
常见报错:RuntimeError: Working outside of application context
这个错误几乎都发生在测试里直接调用了需要 current_app 或 g 的代码,比如 url_for()、db.session.add(),但没进 app.app_context()。
不是所有地方加 with app.app_context(): 就完事 —— 更稳妥的是把 DB 操作、路由生成等收口到视图函数或 service 层,由测试客户端触发完整请求链路,而非在测试函数里手撕逻辑。
- 在 fixture 里进 context 并 yield client,比在每个测试函数里重复写更干净
- 如果必须在测试中访问
current_app,明确加 context:with app.app_context(): assert current_app.debug is False - 避免在模块顶层或
__init__.py中执行依赖 app context 的代码 —— 导入时就可能炸
TESTING 只是开关,真正影响行为的还有 WTF_CSRF_ENABLED=False(跳过表单校验)、LOGIN_DISABLED=True(绕过 Flask-Login 中间件)等。这些不显式关掉,测试会卡在非业务逻辑上。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











