测试

Flask 内置测试客户端(test client),无需真正启动 HTTP 服务器即可对路由和业务逻辑做单元测试与集成测试。配合 pytest 使用体验最佳。

安装依赖

pip install pytest pytest-cov

项目结构

project/
  app/
    __init__.py      # create_app 工厂
  tests/
    conftest.py      # 共享夹具
    test_routes.py
    test_models.py

测试夹具(conftest.py)

应用工厂模式在测试中的价值就体现在这里——为测试创建独立配置的应用实例:

# tests/conftest.py
import pytest
from app import create_app
from app.extensions import db

@pytest.fixture()
def app():
    app = create_app()
    app.config.update(
        TESTING=True,                                   # 关闭错误捕获,异常直接抛给测试
        SQLALCHEMY_DATABASE_URI="sqlite:///:memory:",   # 测试用内存数据库
        WTF_CSRF_ENABLED=False,                         # 测试表单时关闭 CSRF
    )
    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()

@pytest.fixture()
def client(app):
    return app.test_client()

@pytest.fixture()
def runner(app):
    return app.test_cli_runner()    # 测试自定义 flask CLI 命令

测试路由

# tests/test_routes.py
def test_index(client):
    res = client.get("/")
    assert res.status_code == 200
    assert b"Hello" in res.data

def test_create_user_api(client):
    res = client.post("/api/v1/users", json={"name": "Alice", "email": "a@example.com"})
    assert res.status_code == 201
    assert res.get_json()["name"] == "Alice"   # 响应也有 get_json()

def test_404(client):
    res = client.get("/no-such-page")
    assert res.status_code == 404

client.get/post/put/delete 支持 json=(自动序列化并设置 Content-Type)、data=(表单)、headers=query_string= 等参数。

测试登录态

session_transaction() 可以在请求外直接读写 session:

def test_dashboard_requires_login(client):
    res = client.get("/dashboard")
    assert res.status_code == 302               # 未登录被重定向

def test_dashboard_logged_in(client):
    with client.session_transaction() as sess:
        sess["uid"] = 1
    res = client.get("/dashboard")
    assert res.status_code == 200

测试需要应用上下文的代码

视图之外的代码(模型方法、工具函数)若用到 current_app、数据库等,需要手动推入上下文:

def test_model(app):
    with app.app_context():
        user = User(name="Bob")
        db.session.add(user)
        db.session.commit()
        assert user.id is not None

运行与覆盖率

pytest                                        # 运行全部测试
pytest tests/test_routes.py -k create -v      # 按名称过滤
pytest --cov=app --cov-report=term-missing    # 覆盖率报告,标出未覆盖行号

实践建议

  • 每个测试保持独立:依赖内存数据库 + 夹具中建表/删表,避免测试间互相污染。
  • 优先测试行为(状态码、响应内容、数据库副作用),而不是内部实现细节。
  • 外部服务(邮件、第三方 API)用 unittest.mockresponses 库打桩。