单元测试:pytest 编写高覆盖率 Flask 测试

📂 所属阶段:第五阶段 — 高级进阶(性能与架构)
🔗 相关章节:Flask-Login 实战 · 环境搭建


1. pytest 基础

1.1 安装

pip install pytest pytest-flask

1.2 测试配置

# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --tb=short

2. 测试夹具(Fixtures)

# conftest.py(全局测试配置)
import pytest
from app import create_app
from app.extensions import db

@pytest.fixture
def app():
    """创建测试应用"""
    app = create_app("testing")
    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()

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

@pytest.fixture
def authenticated_client(client):
    """已登录的测试客户端"""
    # 注册并登录
    client.post("/auth/register", data={
        "email": "test@example.com",
        "password": "password123",
        "username": "testuser"
    })
    client.post("/auth/login", data={
        "email": "test@example.com",
        "password": "password123"
    })
    return client

3. 测试视图

# tests/test_auth.py
def test_index_page(client):
    """测试首页"""
    response = client.get("/")
    assert response.status_code == 200
    assert b"道满博客" in response.data

def test_login_page(client):
    """测试登录页"""
    response = client.get("/auth/login")
    assert response.status_code == 200

def test_login_success(client, app):
    """测试成功登录"""
    # 先注册
    client.post("/auth/register", data={
        "email": "alice@example.com",
        "password": "SecurePass123!",
        "username": "alice"
    })
    # 再登录
    response = client.post("/auth/login", data={
        "email": "alice@example.com",
        "password": "SecurePass123!"
    }, follow_redirects=True)
    assert response.status_code == 200
    assert b"登录成功" in response.data

def test_login_wrong_password(client):
    """测试密码错误"""
    response = client.post("/auth/login", data={
        "email": "test@example.com",
        "password": "wrongpassword"
    })
    assert response.status_code == 200
    assert b"邮箱或密码错误" in response.data

def test_register_validation(client):
    """测试注册表单验证"""
    response = client.post("/auth/register", data={
        "email": "not-an-email",
        "password": "123",
        "username": "t"
    })
    assert response.status_code == 200
    assert b"email" in response.data or b"邮箱" in response.data

4. 测试受保护路由

# tests/test_articles.py
def test_create_article_requires_login(client):
    """创建文章需要登录"""
    response = client.post("/articles/create", data={
        "title": "Test",
        "content": "Test content"
    })
    assert response.status_code == 302  # 重定向到登录页

def test_create_article_authenticated(authenticated_client):
    """登录用户可以创建文章"""
    response = authenticated_client.post("/articles/create", data={
        "title": "Test Article",
        "content": "This is a test article content."
    }, follow_redirects=True)
    assert response.status_code == 200
    assert b"Test Article" in response.data

5. 测试覆盖率

pip install pytest-cov

# 运行测试并生成覆盖率报告
pytest --cov=app --cov-report=html

# 打开报告
# htmlcov/index.html

# 终端查看
pytest --cov=app --cov-report=term-missing

6. 小结

# pytest 速查

@pytest.fixture     # 测试夹具
client.get(url)     # GET 请求
client.post(url, data={...})  # POST 请求
response.status_code  # 状态码
response.data         # 响应体(bytes)
response.location     # 重定向 URL
follow_redirects=True # 跟随重定向

💡 最佳实践:测试驱动开发(TDD)——先写测试,再写功能代码。覆盖率目标:核心业务 80%+,简单函数 100%。


🔗 扩展阅读