现在位置: 首页 > Flask 教程 > 正文

Flask 测试

测试是保证代码质量的基础。

Flask 提供了内置的测试客户端,配合 pytest 可以高效编写和运行测试。


测试客户端——test_client

Flask 的 test_client() 方法创建了一个模拟的 HTTP 客户端,可以在不启动服务器的情况下发送请求:

实例

# 文件路径:test_demo.py(在 Python 交互环境中测试)
from app import create_app

# 创建测试用的 app 实例
app = create_app()

# 创建测试客户端
with app.test_client() as client:
    # 发送 GET 请求
    response = client.get("/")
    print(response.status_code)   # 200
    print(response.data[:100])    # 响应体的前 100 个字节

    # 发送 POST 请求并以 JSON 发送数据
    response = client.post("/api/posts",
                          json={"title": "RUNOOB 教程", "body": "内容", "author_id": 1})
    print(response.status_code)   # 201
    print(response.get_json())    # 解析 JSON 响应

pytest 集成

pytest 是 Python 社区的标准测试框架,配合 Flask 使用非常方便。

安装 pytest

(.venv) $ pip install pytest

编写测试——conftest.py(共享夹具)

使用 conftest.py 文件创建可复用的测试夹具(fixture):

实例

# 文件路径:tests/conftest.py
import os
import tempfile
import pytest
from app import create_app
from db import init_db, get_db

@pytest.fixture
def app():
    """创建测试用的 app 实例,使用临时数据库"""
    # 创建临时文件作为测试数据库
    db_fd, db_path = tempfile.mkstemp()

    # 创建 app,覆盖数据库路径到临时文件
    app = create_app()
    app.config["DATABASE"] = db_path
    app.config["TESTING"] = True  # 启用测试模式

    # 初始化测试数据库表结构
    with app.app_context():
        init_db()

    yield app

    # 测试后清理:关闭并删除临时数据库文件
    os.close(db_fd)
    os.unlink(db_path)

@pytest.fixture
def client(app):
    """创建测试客户端"""
    return app.test_client()

@pytest.fixture
def runner(app):
    """创建 CLI 测试运行器"""
    return app.test_cli_runner()

测试路由

实例

# 文件路径:tests/test_app.py
def test_home_page(client):
    """测试首页是否正常返回"""
    response = client.get("/")
    assert response.status_code == 200
    # 检查页面是否包含预期关键字
    assert b"RUNOOB" in response.data

def test_404_page(client):
    """测试访问不存在的页面"""
    response = client.get("/this-page-does-not-exist")
    assert response.status_code == 404

运行测试:

(.venv) $ pytest tests/test_app.py -v
====================================
tests/test_app.py::test_home_page PASSED
tests/test_app.py::test_404_page PASSED
====================================

测试 JSON API

实例

# 文件路径:tests/test_api.py
def test_create_post(client):
    """测试创建文章 API"""
    response = client.post("/api/posts", json={
        "title": "RUNOOB 测试文章",
        "body": "这是测试内容",
        "author_id": 1
    })
    # 创建成功应返回 201
    assert response.status_code == 201
    data = response.get_json()
    assert data["title"] == "RUNOOB 测试文章"
    assert "id" in data

def test_create_post_without_title(client):
    """测试标题为空时返回 400 错误"""
    response = client.post("/api/posts", json={
        "title": "",
        "body": "内容",
        "author_id": 1
    })
    assert response.status_code == 400
    assert b"error" in response.data

def test_get_posts(client):
    """测试获取文章列表"""
    # 先创建一篇文章
    client.post("/api/posts", json={
        "title": "文章 A", "body": "内容 A", "author_id": 1
    })

    # 获取列表
    response = client.get("/api/posts")
    assert response.status_code == 200
    data = response.get_json()
    assert len(data) >= 1

def test_delete_post(client):
    """测试删除文章"""
    # 先创建
    rv = client.post("/api/posts", json={
        "title": "待删除", "body": "内容", "author_id": 1
    })
    post_id = rv.get_json()["id"]

    # 再删除
    rv = client.delete(f"/api/posts/{post_id}")
    assert rv.status_code == 200

    # 确认已删除
    rv = client.get(f"/api/posts/{post_id}")
    assert rv.status_code == 404

测试 Session

测试登录等需要 Session 的功能:

实例

# 文件路径:tests/test_auth.py
def test_login(client):
    """测试登录流程"""
    # 发送登录请求
    response = client.post("/auth/login", data={
        "username": "testuser",
        "password": "testpass"
    }, follow_redirects=True)  # follow_redirects 自动跟随重定向

    assert response.status_code == 200

def test_session_transaction(client):
    """直接操作 session 进行测试"""
    with client.session_transaction() as session:
        # 直接在 session 中设置值,模拟已登录状态
        session["username"] = "runoob"

    # 现在访问首页,应该显示已登录状态
    response = client.get("/")
    assert b"runoob" in response.data

client.session_transaction() 是一个非常实用的测试工具——它让你可以直接修改 Session 内容,无需通过完整的登录流程。


测试 CLI 命令

实例

# 文件路径:tests/test_cli.py
def test_init_db(runner):
    """测试数据库初始化命令"""
    result = runner.invoke(args=["init-db"])
    # 命令执行成功,exit_code 应为 0
    assert result.exit_code == 0

def test_flask_routes(runner):
    """测试 flask routes 命令"""
    result = runner.invoke(args=["routes"])
    assert result.exit_code == 0
    # 检查输出是否包含注册的路由
    assert "/api/posts" in result.output

测试最佳实践

实践 说明
使用临时数据库 每次测试用独立数据库,互不干扰(如 conftest.py 中的 tempfile)
开启 TESTING 模式 app.config["TESTING"] = True 让异常正常传播而非被错误处理吞掉
一个测试只测一件事 每个 test 函数只验证一个行为,便于定位问题
命名清晰 函数名形如 test_什么场景_预期什么结果
善用 follow_redirects 测试 POST 后重定向的流程时很有用

运行全部测试

(.venv) $ pytest tests/ -v

tests/test_app.py::test_home_page PASSED
tests/test_app.py::test_404_page PASSED
tests/test_api.py::test_create_post PASSED
tests/test_api.py::test_create_post_without_title PASSED
tests/test_api.py::test_get_posts PASSED
tests/test_api.py::test_delete_post PASSED
tests/test_auth.py::test_login PASSED
tests/test_auth.py::test_session_transaction PASSED
tests/test_cli.py::test_init_db PASSED
tests/test_cli.py::test_flask_routes PASSED

==================== 10 passed in 0.45s ====================