Pourquoi Tester FastAPI avec Pytest ?
Tester FastAPI avec Pytest est essentiel pour les développeurs car il permet de s'assurer que l'application fonctionne comme prévu et qu'elle reste robuste à mesure du temps. En outre, cela aide à détecter rapidement les bugs ou les anomalies avant qu'ils ne deviennent problématiques.
Un cas d'usage concret est lorsque vous travaillez sur une API de gestion des utilisateurs. Un test automatique peut vérifier que le système crée un utilisateur avec succès et renvoie la bonne réponse HTTP, tout en s'assurant que les données sont correctement stockées dans la base de données.
Prerequis
- Connaissances en FastAPI
- Connaissance de Python (3.7+ recommandé)
- Installation de Pytest (
pip install pytest) - Un environnement de développement (IDE, éditeur de texte)
Concepts fondamentaux
1. Structure du Test
La structure d'un test est simple : il doit appeler une fonction qui teste une partie spécifique du code et vérifier les résultats.
## myapp/tests/test_main.py
import pytest
from main import app
def test_read_root():
response = app.test_client().get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
2. Fixtures
Les fixtures sont des fonctions qui fournissent des données ou des objets que les tests utilisent. Elles facilitent la réutilisation du code et permettent une meilleure isolation.
## myapp/tests/conftest.py
from fastapi.testclient import TestClient
import main
@pytest.fixture(scope="module")
def test_client():
app = main.app
with TestClient(app) as client:
yield client
3. Assertions
Pytest utilise des assertions pour vérifier les conditions de tes tests.
## myapp/tests/test_main.py
import pytest
from main import app
def test_read_root(test_client):
response = test_client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
4. Paramètres de Tests
Pytest prend en charge les tests paramétrisés, ce qui permet d'exécuter le même test avec différents arguments.
## myapp/tests/test_main.py
import pytest
from main import app
@pytest.mark.parametrize("path, expected", [
("/", {"message": "Hello World"}),
("/items/1", {"item_id": 1}),
])
def test_read_path(test_client, path, expected):
response = test_client.get(path)
assert response.status_code == 200
assert response.json() == expected
Mise en pratique : Projet fil rouge
Dans ce tutoriel, nous allons créer un simple gestionnaire de tâches avec FastAPI et Pytest. L'application permettra d'afficher une liste de tâches et de les marquer comme terminées.
Étape 1 : Configuration du projet
Créez un nouveau dossier pour le projet et initialisez-le avec Python.
mkdir task_manager
cd task_manager
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn pytest
Étape 2 : Création de l'application FastAPI
Créez un fichier main.py pour définir les routes et le modèle.
## myapp/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Task(BaseModel):
id: int
description: str
completed: bool = False
tasks = []
@app.get("/")
async def read_root():
return {"message": "Bienvenue au Gestionnaire de Tâches"}
@app.post("/tasks/", response_model=Task)
async def create_task(task: Task):
tasks.append(task)
return task
@app.get("/tasks/{task_id}", response_model=Task)
async def read_task(task_id: int):
for task in tasks:
if task.id == task_id:
return task
raise HTTPException(status_code=404, detail="Tâche non trouvée")
Étape 3 : Création des tests
Créez un dossier tests et ajoutez les fichiers de test.
## myapp/tests/test_main.py
import pytest
from main import app, tasks, Task
from fastapi.testclient import TestClient
@pytest.fixture(scope="module")
def test_client():
with TestClient(app) as client:
yield client
@pytest.fixture(autouse=True)
def clean_tasks():
global tasks
tasks = []
def test_read_root(test_client):
response = test_client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Bienvenue au Gestionnaire de Tâches"}
def test_create_task(test_client):
task_data = Task(id=1, description="Faire les courses")
response = test_client.post("/tasks/", json=task_data.dict())
assert response.status_code == 200
assert response.json() == task_data.dict()
def test_read_task(test_client):
task_data = Task(id=1, description="Faire les courses", completed=True)
response = test_client.post("/tasks/", json=task_data.dict())
assert response.status_code == 200
read_response = test_client.get(f"/tasks/{task_data.id}")
assert read_response.status_code == 200
assert read_response.json() == task_data.dict()
Étape 4 : Exécution des tests
Exécutez les tests avec la commande suivante :
pytest myapp/tests/
Erreurs fréquentes et debugging
Erreur 1 : AttributeError: 'NoneType' object has no attribute 'status_code'
Cela se produit si vous essayez d'accéder à un attribut de la réponse avant qu'elle ne soit initialisée.
## ❌ Mauvais
response = app.test_client().get("/")
assert response.status_code == 200 # Erreur car app.test_client() est None
## ✅ Correct
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
Erreur 2 : KeyError: 'task_id'
Cela se produit si vous essayez d'accéder à une clé qui n'existe pas dans le dictionnaire de la réponse.
## ❌ Mauvais
response = test_client.get("/tasks/1")
assert response.json()["task_id"] == 1 # Erreur car 'task_id' n'existe pas
## ✅ Correct
response = test_client.get("/tasks/1")
assert response.status_code == 200
assert response.json()["id"] == 1
Erreur 3 : AssertionError: assert False
Cela se produit si votre assertion échoue.
## ❌ Mauvais
response = test_client.get("/tasks/1")
assert response.status_code == 204 # Erreur car le code est 200
## ✅ Correct
response = test_client.get("/tasks/1")
assert response.status_code == 200
Pour aller plus loin
1. Tests Asynchrones avec pytest-asyncio
Pour tester des applications FastAPI asynchrones, utilisez le plugin pytest-asyncio.
pip install pytest-asyncio
Ajoutez la configuration suivante dans votre fichier pytest.ini :
[pytest]
addopts = --asyncio-mode=auto
2. Coverage des Tests
Utilisez un outil de couverture pour vérifier combien de code est testé.
pip install pytest-cov
pytest myapp/tests/ --cov=myapp
3. Fixtures Avancées
Explorez les fixtures avancées comme autouse, scope="session", et les fixtures paramétrisées.
Défi Pratique
Créez un API simple de blog avec FastAPI qui permet d'ajouter, lire, mettre à jour et supprimer des articles. Ajoutez des tests pour chaque fonctionnalité.