跳到主要内容

测试

Flask 提供了测试客户端和与 pytest 的集成,方便进行单元测试和集成测试。

安装

pip install pytest pytest-flask coverage

测试配置

# config.py
class Config:
SECRET_KEY = 'dev-key'
SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db'

class TestConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
WTF_CSRF_ENABLED = False

测试固件

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

@pytest.fixture
def app():
app = create_app(TestConfig)

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()

基本测试

测试路由

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

def test_404(client):
response = client.get('/nonexistent')
assert response.status_code == 404

测试表单

def test_login_success(client):
response = client.post('/auth/login', data={
'username': 'testuser',
'password': 'testpass'
}, follow_redirects=True)

assert response.status_code == 200
assert b'登录成功' in response.data

def test_login_failure(client):
response = client.post('/auth/login', data={
'username': 'wrong',
'password': 'wrong'
})

assert b'用户名或密码错误' in response.data

数据库测试

# tests/test_models.py
from app.models.user import User

def test_user_creation(app):
with app.app_context():
user = User(username='test', email='[email protected]')
user.set_password('password')
db.session.add(user)
db.session.commit()

assert user.id is not None
assert user.check_password('password')
assert not user.check_password('wrong')

def test_user_unique_constraint(app):
with app.app_context():
user1 = User(username='test', email='[email protected]')
user2 = User(username='test', email='[email protected]')

db.session.add(user1)
db.session.commit()

db.session.add(user2)
with pytest.raises(Exception):
db.session.commit()

认证测试

# tests/test_auth.py
import pytest
from flask_login import current_user

def test_login_required(client):
response = client.get('/dashboard', follow_redirects=True)
assert b'请先登录' in response.data

class TestAuth:
@pytest.fixture(autouse=True)
def setup_user(self, app):
with app.app_context():
user = User(username='test', email='[email protected]')
user.set_password('testpass')
db.session.add(user)
db.session.commit()
self.user_id = user.id

def test_login(self, client):
response = client.post('/auth/login', data={
'username': 'test',
'password': 'testpass'
}, follow_redirects=True)

assert response.status_code == 200

def test_logout(self, client):
# 先登录
client.post('/auth/login', data={
'username': 'test',
'password': 'testpass'
})

# 再登出
response = client.get('/auth/logout', follow_redirects=True)
assert b'已退出登录' in response.data

API 测试

# tests/test_api.py
import json

def test_get_users(client):
response = client.get('/api/v1/users')
assert response.status_code == 200
data = json.loads(response.data)
assert isinstance(data, list)

def test_create_user(client):
response = client.post('/api/v1/users',
data=json.dumps({
'username': 'newuser',
'email': '[email protected]',
'password': 'newpass'
}),
content_type='application/json'
)

assert response.status_code == 201
data = json.loads(response.data)
assert data['username'] == 'newuser'

def test_update_user(client, app):
with app.app_context():
user = User.query.first()

response = client.put(f'/api/v1/users/{user.id}',
data=json.dumps({'username': 'updated'}),
content_type='application/json'
)

assert response.status_code == 200
data = json.loads(response.data)
assert data['username'] == 'updated'

使用 Test Client

def test_with_session(client):
with client.session_transaction() as sess:
sess['user_id'] = 1

response = client.get('/profile')
assert response.status_code == 200

def test_file_upload(client):
data = {
'file': (io.BytesIO(b'my file contents'), 'test.txt')
}
response = client.post('/upload', data=data, content_type='multipart/form-data')
assert response.status_code == 200

覆盖率测试

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

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

测试组织

tests/
├── conftest.py
├── test_auth.py
├── test_models.py
├── test_routes.py
├── test_api.py
└── test_forms.py

测试最佳实践

  1. 每个测试独立: 测试之间不应相互依赖
  2. 使用固件: 复用测试数据和配置
  3. 测试边界情况: 空值、最大值、异常输入
  4. 保持测试快速: 使用内存数据库
  5. 命名清晰: 测试函数名描述测试内容

下一步

学习如何部署 Flask 应用到生产环境。