Flask 笔记十六:用 pytest 测 Flask 应用

发布时间:2026/6/27 19:44:30
Flask 笔记十六:用 pytest 测 Flask 应用 上一篇我们把flask create-admin、flask db upgrade写进了部署流程。功能越堆越多改一处、怕别处坏——手动点浏览器很快不够用了。这一篇做一件事用 pytest Flask 测试客户端给路由和 JSON API 写自动化测试。例子仍是通用的Note备忘录项目不涉及任何真实业务。1. 学完后你能做什么用pytest跑测试比 unittest 写法更短用app.test_client()模拟 HTTP 请求不真开浏览器测试 登录、受保护页面、JSON API用 临时 SQLite 内存库测试不污染开发数据库用 fixture 复用「已登录客户端」2. 为什么需要测试方式优点缺点手动点页面直观慢、易漏、难回归脚本 curl适合 API难组「先登录再访问」pytest test_client快、可重复、改完一键跑要先写一点代码入门目标改完代码跑pytest30 秒内知道「列表页还能不能打开、未登录会不会 401」。3. 安装依赖pip install pytest可选覆盖率后面再用pip install pytest-cov项目根目录建tests/文件夹myproject/├── app/├── tests/│ ├── __init__.py # 空文件即可│ ├── conftest.py # 公共 fixture│ └── test_notes.py # 具体测试├── pytest.ini # 可选配置└── requirements-dev.txt # 可选pytest 放这里4. 测试用的 App内存数据库测试 不要连开发用的 MySQL/SQLite 文件否则可能删光数据。常见做法测试配置里用 内存 SQLitetests/conftest.pyimport pytestfrom app import app as flask_app, dbfrom app.models import User, Notepytest.fixturedef app():flask_app.config.update({TESTING: True,SQLALCHEMY_DATABASE_URI: sqlite:///:memory:,SECRET_KEY: test-secret,WTF_CSRF_ENABLED: False, # 测试里简化 POST见下文})with flask_app.app_context():db.create_all()yield flask_appdb.session.remove()db.drop_all()pytest.fixturedef client(app):return app.test_client()pytest.fixturedef user(app):from werkzeug.security import generate_password_hashu User(nametester, pwdgenerate_password_hash(pass123), is_adminFalse)db.session.add(u)db.session.commit()return upytest.fixturedef auth_client(client, user):已登录的 test client。client.post(/login/, data{name: tester,password: pass123,})return client要点TESTING True部分扩展会走测试模式:memory:库在内存里测完drop_allWTF_CSRF_ENABLED False表单 POST 不必每次带 token只限测试生产必须开 CSRF若你坚持测 CSRF可在 POST 里从页面 parsecsrf_token入门先关省事。5. 第一个测试首页能打开tests/test_notes.pydef test_index(client):resp client.get(/)assert resp.status_code 200运行pytest# 或pytest tests/test_notes.py -v-v看每条用例名失败时会打印断言差异。6. 测未登录 vs 已登录假设列表页/notes/有login_requireddef test_note_list_redirects_when_anonymous(client):resp client.get(/notes/, follow_redirectsFalse)assert resp.status_code in (302, 401) # redirect 登录 或 直接 401def test_note_list_ok_when_logged_in(auth_client):resp auth_client.get(/notes/)assert resp.status_code 200assert b备忘录 in resp.data or bnotes in resp.data.lower()follow_redirectsFalse只看 第一步 响应方便断言 302。7. 测 POST新增一条备忘录def test_create_note(auth_client):resp auth_client.post(/notes/add/, data{title: pytest 写的标题,content: 内容,}, follow_redirectsTrue)assert resp.status_code 200assert bpytest in resp.data配合conftest里已登录的auth_client不用手写 Session。8. 测 JSON API接第十三篇def test_api_notes_unauthorized(client):resp client.get(/api/notes/)assert resp.status_code 401data resp.get_json()assert data[ok] is Falsedef test_api_notes_json(auth_client):resp auth_client.get(/api/notes/)assert resp.status_code 200data resp.get_json()assert data[ok] is Trueassert items in dataassert isinstance(data[items], list)get_json()比json.loads(resp.data)简洁。POST JSONdef test_api_create_note(auth_client):resp auth_client.post(/api/notes/,json{title: API 测试, content: },)assert resp.status_code 201data resp.get_json()assert data[ok] is Trueassert data[item][title] API 测试9. 在测试里准备数据def test_list_shows_own_notes_only(app, auth_client, user):other User(nameother, pwdx)db.session.add(other)db.session.flush()db.session.add(Note(title我的, user_iduser.id))db.session.add(Note(title别人的, user_idother.id))db.session.commit()resp auth_client.get(/notes/)assert b我的 in resp.dataassert b别人的 not in resp.data业务规则只看自己的备忘录适合写成测试以后改note_service时不容易回归。10.pytest.ini可选项目根目录[pytest]testpaths testspython_files test_*.pyaddopts -v --tbshort以后只打pytest即可。11. 和 note_service 的关系接第十篇视图越薄测试越好写# tests/test_note_service.pydef test_list_notes_for_user_filters_by_owner(app, user):from app.note_service import list_notes_for_userfrom app.models import Note, Userother User(nameo, pwdx)db.session.add_all([other, Note(titlea, user_iduser.id)])db.session.commit()page list_notes_for_user(user.id, page1, per_page10)titles [n.title for n in page.items]assert titles [a]service 测试查数据库、规则对不对client 测试路由、登录、HTTP 状态码、模板/API 格式两层都写会啰嗦入门 先测 client 关键路径复杂查询再抽 service 单测。12. 流程示意pytest│▼conftestapp 内存库 client│▼test_xxxclient.get/post│▼Flask 路由 → 视图 → service → DB内存│▼assert status_code / JSON / 页面字节开发改代码 → pytest → 绿/红│└── 红修完再提交13. 新手常踩的 6 个坑没app.app_context()— fixture 里with flask_app.app_context():包create_all。测试连了真库 — 务必TESTING 内存 URI。CSRF 导致 POST 400 — 测试配置关 CSRF或表单带 token。Session 不共享 — 同一client对象上先post /login/再get /notes/。断言中文乱码 —resp.data是 bytes用b备忘录 in resp.data。测完不清理 — fixture 末尾drop_all或用:memory:。14. 小结记住五件事pytesttests/目录conftest.py里 app / client / auth_client fixture内存 SQLite不碰开发库client.get/postget_json()测 API关键路径未登录 401、登录后 200、只能看自己的数据十六篇下来你已经具备写功能 → 分层 → 配置 → API → 部署 → CLI → 自动化测试 的完整入门闭环。