Flask测试必须设TESTING=True,否则500错误被静默;应使用test_client()而非requests;数据库用内存SQLite或事务回滚;Mock需在应用上下文中进行。

Flask应用测试前必须设置TESTING=True
不设这个配置,app.test_client() 会跳过错误捕获、禁用调试器,导致 500 错误静默失败,测试看似通过实则没跑通逻辑。很多初学者卡在这里,以为路由返回了 200 就万事大吉,其实内部异常被吞掉了。
正确做法是单独为测试准备一个配置或直接在测试前修改:
app.config['TESTING'] = True
# 或继承原配置后覆盖
class TestConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
注意:TESTING=True 不会自动启用 WTF_CSRF_ENABLED=False,表单测试仍可能因 CSRF 报错,需额外处理。
用app.test_client()发请求比启动真实服务更可靠
别用 requests.get('http://localhost:5000/') 测试本地 Flask 服务——它依赖端口空闲、进程存活、上下文隔离,极易受环境干扰。而 test_client() 是纯内存模拟,不走网络栈,能精确控制请求头、session、cookie,还能访问响应后的 response.context(Jinja 模板上下文)和 response.history(重定向链)。
立即学习“Python免费学习笔记(深入)”;
常见用法示例:
with app.test_client() as client:
# 带登录态的请求
with client.session_transaction() as sess:
sess['user_id'] = 123
resp = client.get('/dashboard')
assert resp.status_code == 200
assert b'Welcome' in resp.data
- GET 请求参数用
client.get('/search?q=flask'),URL 编码由 client 自动处理 - POST JSON 数据:用
client.post('/api', json={'name': 'test'}),自动设Content-Type: application/json - 上传文件:用
client.post('/upload', data={'file': (io.BytesIO(b'test'), 'test.txt')})
数据库测试必须用内存 SQLite 或事务回滚
连接真实开发库会导致测试污染数据、并发冲突、清理困难。最简方案是用 sqlite:///:memory:,但要注意它不支持多线程(Flask 默认多线程测试 client 可能触发),所以得显式禁用:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'connect_args': {'check_same_thread': False}}
更稳妥的做法是复用开发数据库 URL,但在每个测试前后用事务控制:
@pytest.fixture
def client():
app.config['TESTING'] = True
with app.app_context():
db.create_all()
yield app.test_client()
db.session.remove()
db.drop_all()
关键点:db.session.remove() 清理 session,否则下个测试可能读到上个测试残留的未提交对象;db.drop_all() 要在 remove() 后,否则报错。
Mock 外部依赖时小心 Flask 的应用上下文生命周期
比如你 mock 了 requests.get,但在视图里用了 current_app.logger.info(),测试时若没进上下文,会抛 RuntimeError: Working outside of application context。不能只靠 @patch,得确保 mock 发生在上下文内。
推荐写法:
def test_api_with_mock(client):
with app.app_context(): # 确保 current_app 可用
with patch('myapp.views.requests.get') as mock_get:
mock_get.return_value.json.return_value = {'ok': True}
resp = client.get('/external')
assert resp.status_code == 200
另一个坑:mock 对象默认不继承原始函数的签名,如果被测函数用了 functools.wraps 或检查 inspect.signature,要用 patch(..., autospec=True)。
真实项目里,数据库模型、第三方 API、缓存(Redis)、消息队列这些都得拆离,否则测试慢、不稳定、难定位问题。接口层只验证输入输出,逻辑层再单独测具体实现。


















