Voici un tutoriel approfondi en français sur la façon de tester Flask avec Pytest.
Pourquoi Tester Flask avec Pytest ?
Le testing est une pratique essentielle pour les développeurs, mais plus particulièrement lorsqu'ils travaillent sur des applications web comme Flask. En effet, le code source peut être complexe et sujet à des bugs qui peuvent être difficiles à repérer sans un bon système de tests.
Un cas d'usage concret est l'évolution continue d'une application en cours de développement. Sans tests automatisés, chaque changement apporté au code peut potentiellement casser le fonctionnement existant. Les tests automatisés permettent de détecter rapidement les problèmes et de s'assurer que tout fonctionne comme prévu.
Prerequis
Pour suivre ce tutoriel, il est recommandé d'avoir les connaissances suivantes :
- Connaissances en Python
- Familiarité avec Flask
- Compréhension des concepts de base de la programmation orientée objet (POO)
Les outils que vous aurez besoin d'installer sont :
- Python 3.7 ou plus récent
- pip, le gestionnaire de packages Python
- Pytest
Vous pouvez installer Pytest en utilisant pip :
pip install pytest
Concepts fondamentaux
1. Fixtures
Les fixtures sont des fonctions qui préparent et initialisent les données nécessaires pour les tests. Elles peuvent être utilisées pour créer des instances de modèles, des environnements de base de données, etc.
import pytest
from myapp import create_app
@pytest.fixture
def app():
app = create_app()
yield app
2. Clients HTTP
Pytest-Fixture peut également être utilisé pour créer un client HTTP pour les tests d'intégration. Cela vous permet de simuler des requêtes HTTP et de vérifier les réponses.
import pytest
from myapp import create_app
@pytest.fixture
def app():
app = create_app()
yield app
@pytest.fixture
def client(app):
with app.test_client() as client:
yield client
3. Assertions
Les assertions sont utilisées pour vérifier que les résultats des tests correspondent aux attentes. Pytest utilise une syntaxe de base pour les assertions.
import pytest
from myapp import create_app, db
@pytest.fixture
def app():
app = create_app()
yield app
@pytest.fixture
def client(app):
with app.test_client() as client:
yield client
@pytest.fixture
def db_setup(client):
# Code pour créer et initialiser la base de données
pass
def test_homepage(client, db_setup):
response = client.get('/')
assert response.status_code == 200
assert b'Welcome to My Flask App!' in response.data
Mise en pratique : projet fil rouge
Nous allons construire un gestionnaire de tâches simple avec Flask et Pytest. Ce projet comprendra une application web permettant d'afficher, ajouter et supprimer des tâches.
Étape 1 : Initialisation du projet
Créez un nouveau dossier pour votre projet et initialisez un environnement virtuel :
mkdir task_manager
cd task_manager
python -m venv venv
source venv/bin/activate
Installez Flask et Pytest :
pip install flask pytest
Étape 2 : Création de l'application
Créez un fichier app.py avec le code suivant :
from flask import Flask, request, jsonify
app = Flask(__name__)
tasks = []
@app.route('/tasks', methods=['GET'])
def get_tasks():
return jsonify(tasks)
@app.route('/tasks', methods=['POST'])
def add_task():
task = request.json
tasks.append(task)
return jsonify(task), 201
@app.route('/tasks/<int:index>', methods=['DELETE'])
def delete_task(index):
if index < len(tasks):
del tasks[index]
return '', 204
return 'Task not found', 404
Étape 3 : Création des tests
Créez un fichier test_app.py avec le code suivant :
import pytest
from app import create_app, db
@pytest.fixture
def app():
app = create_app()
yield app
@pytest.fixture
def client(app):
with app.test_client() as client:
yield client
@pytest.fixture
def db_setup(client):
# Code pour créer et initialiser la base de données
pass
def test_get_tasks(client, db_setup):
response = client.get('/tasks')
assert response.status_code == 200
assert len(response.json) == 0
def test_add_task(client, db_setup):
task = {'name': 'Buy groceries', 'done': False}
response = client.post('/tasks', json=task)
assert response.status_code == 201
assert response.json['name'] == 'Buy groceries'
def test_delete_task(client, db_setup):
task = {'name': 'Clean the house', 'done': True}
client.post('/tasks', json=task)
response = client.delete('/tasks/0')
assert response.status_code == 204
Étape 4 : Exécution des tests
Exécutez les tests en utilisant la commande suivante :
pytest
Erreurs frequentes et debugging
Voici quelques erreurs fréquentes que vous pourriez rencontrer lors de l'utilisation de Pytest avec Flask, ainsi que leurs corrections.
1. Erreur : AttributeError: 'NoneType' object has no attribute 'get_json'
Cette erreur se produit lorsque vous essayez d'appeler la méthode get_json() sur un objet request qui n'est pas défini. Assurez-vous de vérifier si le client a effectivement envoyé des données JSON.
## ❌ Mauvais
task = request.get_json()
## ✅ Correct
if request.is_json:
task = request.get_json()
else:
return 'Invalid request', 400
2. Erreur : AssertionError: assert b'Welcome to My Flask App!' not in response.data
Cette erreur se produit lorsque vous essayez de vérifier que la réponse contient une chaîne spécifique, mais elle n'est pas présente. Assurez-vous d'utiliser le bon encodage et de vérifier les données binaires.
## ❌ Mauvais
assert b'Welcome to My Flask App!' in response.data
## ✅ Correct
assert 'Welcome to My Flask App!' in response.get_data(as_text=True)
3. Erreur : TypeError: Object of type 'NoneType' is not iterable
Cette erreur se produit lorsque vous essayez d'énumérer un objet qui est None. Assurez-vous de vérifier que l'objet n'est pas None avant de le parcourir.
## ❌ Mauvais
for task in tasks:
# Code
## ✅ Correct
if tasks is not None:
for task in tasks:
# Code
else:
return 'No tasks found', 404
Pour aller plus loin
1. Coverage de tests
Pour mesurer la couverture des tests, vous pouvez utiliser l'outil pytest-cov. Installez-le et configurez-le :
pip install pytest-cov
Ajoutez la ligne suivante à votre fichier pytest.ini :
[pytest]
addopts = --cov=myapp
Exécutez les tests avec coverage :
pytest
2. Fixtures paramétrées
Les fixtures peuvent être paramétrées pour tester différents scénarios. Utilisez la fonction parametrize de Pytest.
import pytest
from app import create_app, db
@pytest.fixture(params=[{'name': 'Buy groceries', 'done': False}, {'name': 'Clean the house', 'done': True}])
def task(request):
return request.param
def test_add_task(client, task):
response = client.post('/tasks', json=task)
assert response.status_code == 201
assert response.json['name'] == task['name']
3. Fixtures avec scope local et session
Vous pouvez définir différents scopes pour les fixtures : function, class, module et session. Le scope=session permet de créer une fixture qui est exécutée une seule fois pour tous les tests.
import pytest
from app import create_app, db
@pytest.fixture(scope='session')
def app():
app = create_app()
yield app
@pytest.fixture(scope='session')
def client(app):
with app.test_client() as client:
yield client
def test_add_task(client):
task = {'name': 'Buy groceries', 'done': False}
response = client.post('/tasks', json=task)
assert response.status_code == 201
assert response.json['name'] == 'Buy groceries'
Défi pratique
Créez un simple API de blog avec Flask et Pytest. L'API devrait permettre d'afficher, ajouter et supprimer des articles.