Pourquoi Tests unitaires en Python ?
Les tests unitaires sont essentiels pour les développeurs Python, car ils aident à garantir que chaque partie de votre code fonctionne correctement indépendamment des autres parties. En pratique quotidienne, cela signifie que vous pouvez travailler sur différents aspects du même projet sans avoir peur de régression. Par exemple, si vous ajoutez une nouvelle fonctionnalité dans un module existant, les tests unitaires peuvent détecter immédiatement si cette nouvelle fonctionnalité affecte les fonctionnalités existantes.
Prerequis
- Connaissance de base du langage Python
- Installation de Python (version 3.8 ou plus récente recommandée)
- Installation de
pytestpour exécuter les tests (en utilisant la commande suivante :pip install pytest)
Concepts fondamentaux
Concept 1 : Fonction assert
La fonction assert est un outil de base pour écrire des tests unitaires. Elle vérifie si une condition est vraie, sinon elle lève une exception AssertionError.
def add(a, b):
return a + b
def test_add():
# Vérifie que la fonction add retourne le bon résultat
assert add(2, 3) == 5
Concept 2 : Fonctions de test
Les fonctions de test sont des fonctions qui ne font qu'un seul chose : vérifier un aspect spécifique du code. Elles doivent commencer par test_.
def test_upper():
# Vérifie que la méthode upper() convertit une chaîne en majuscules
assert 'foo'.upper() == 'FOO'
Concept 3 : Bibliothèque pytest
pytest est une bibliothèque populaire pour écrire et exécuter des tests unitaires. Il offre une syntaxe simple et puissante.
import pytest
def test_divide():
# Vérifie que la division par zéro lève une exception ValueError
with pytest.raises(ValueError):
def divide(x, y): return x / y
divide(10, 0)
Concept 4 : Fixtures
Les fixtures sont des fonctions qui fournissent des données ou un environnement pour les tests. Elles permettent de créer des conditions préalables avant chaque test.
import pytest
@pytest.fixture
def sample_data():
# Retourne une liste de nombres pour les tests
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
# Vérifie que la somme des éléments est correcte
assert sum(sample_data) == 15
Mise en pratique : Projet fil rouge
Nous allons construire un simple gestionnaire de tâches qui permet d'ajouter, de supprimer et de lister les tâches. Voici la structure du projet :
todo_manager/
├── __init__.py
├── task.py
└── test_task.py
Étape 1 : Création des fichiers
Créez les fichiers __init__.py, task.py et test_task.py.
from .task import TaskManager
python##
class Task:
def __init__(self, description):
self.description = description
class TaskManager:
def __init__(self):
self.tasks = []
def add_task(self, task):
self.tasks.append(task)
def remove_task(self, index):
if 0 <= index < len(self.tasks):
del self.tasks[index]
def list_tasks(self):
return [task.description for task in self.tasks]
python##
from ..task import TaskManager
def test_add_task():
manager = TaskManager()
manager.add_task(Task("Acheter du pain"))
assert len(manager.list_tasks()) == 1
def test_remove_task():
manager = TaskManager()
manager.add_task(Task("Faire la lessive"))
manager.remove_task(0)
assert len(manager.list_tasks()) == 0
Étape 2 : Exécution des tests
Exécutez les tests avec pytest.
pytest todo_manager/test_task.py
Erreurs fréquentes et debugging
Erreur 1 : Test échoue sans raison
Code incorrect :
def test_upper():
assert 'foo'.upper() == 'FOO'
Code correct :
def test_upper():
assert 'foo'.upper() == 'FOO' # Correct
Erreur 2 : IndexError lors de la suppression d'une tâche
Code incorrect :
def remove_task(self, index):
del self.tasks[index]
Code correct :
def remove_task(self, index):
if 0 <= index < len(self.tasks):
del self.tasks[index] # Ajout de la vérification d'index valide
Erreur 3 : Test échoue à cause d'une mauvaise implémentation
Code incorrect :
def list_tasks(self):
return [task.description for task in self.tasks]
Code correct :
def list_tasks(self):
return [task.description for task in self.tasks] # Correct
Pour aller plus loin
Piste 1 : Tests pour les exceptions
Ajoutez des tests pour vérifier le comportement de votre code en cas d'exceptions.
- Exercice : Ajouter un test pour vérifier que
remove_tasklève une exception si l'index est hors de portée.
Piste 2 : Mocking
Utilisez le mocking pour isoler les dépendances et tester uniquement le comportement de votre code.
- Exercice : Utiliser
unittest.mockpour simuler un appel à une autre méthode dans le gestionnaire de tâches.
Piste 3 : Couverture des tests
Améliorez la couverture de vos tests en ajoutant plus de cas d'utilisation et en testant les conditions limites.
- Exercice : Ajouter des tests pour vérifier le comportement du gestionnaire de tâches avec une liste vide.
Défi pratique
Défi : Créer un simple API RESTful en utilisant Flask qui permet de créer, lire, mettre à jour et supprimer des tâches. Écrivez les tests unitaires pour chaque endpoint.
- Utilisez
pytestpour exécuter les tests. - Pour le mocking des requêtes HTTP, utilisez
requests-mock.