Maîtrisez unittest.mock de Python. Plongez dans les test doubles, mocks, stubs, fakes et le décorateur patch pour des tests unitaires robustes et isolés.
Objets Mock Python : Un guide complet sur l'implémentation des Test Doubles
Dans le monde du développement logiciel moderne, écrire du code n'est que la moitié de la bataille. S'assurer que le code est fiable, robuste et fonctionne comme prévu est l'autre moitié, tout aussi critique. C'est là qu'interviennent les tests automatisés. Les tests unitaires, en particulier, sont une pratique fondamentale qui consiste à tester des composants individuels ou des 'unités' d'une application de manière isolée. Cependant, cette isolation est souvent plus facile à dire qu'à faire. Les applications du monde réel sont des toiles complexes d'objets, de services et de systèmes externes interconnectés. Comment tester une seule fonction si elle dépend d'une base de données, d'une API tierce ou d'une autre partie complexe de votre système ?
La réponse réside dans une technique puissante : l'utilisation de Test Doubles. Et dans l'écosystème Python, l'outil principal pour les créer est la bibliothèque polyvalente et indispensable unittest.mock. Ce guide vous emmènera dans une exploration approfondie du monde des mocks et des test doubles en Python. Nous explorerons leur "pourquoi", démystifierons les différents types et fournirons des exemples pratiques et réels utilisant unittest.mock pour vous aider à écrire des tests plus propres, plus rapides et plus efficaces.
Que sont les Test Doubles et pourquoi en avons-nous besoin ?
Imaginez que vous construisez une fonction qui récupère le profil d'un utilisateur depuis la base de données de votre entreprise, puis le formate. La signature de la fonction pourrait ressembler à ceci : get_formatted_user_profile(user_id, db_connection).
Pour tester cette fonction, vous faites face à plusieurs défis :
- Dépendance à un système réel : Votre test aurait besoin d'une base de données en cours d'exécution. Cela rend les tests lents, complexes à configurer et dépendants de l'état et de la disponibilité d'un système externe.
- Imprévisibilité : Les données de la base de données pourraient changer, ce qui ferait échouer votre test même si votre logique de formatage est correcte. Cela rend les tests "instables" ou non déterministes.
- Difficulté à tester les cas limites : Comment tester ce qui se passe si la connexion à la base de données échoue, ou si elle renvoie un utilisateur auquel il manque certaines données ? Simuler ces scénarios spécifiques avec une base de données réelle peut être incroyablement difficile.
Un Test Double est un terme générique désignant tout objet qui remplace un objet réel pendant un test. En remplaçant le vrai db_connection par un test double, nous pouvons rompre la dépendance vis-à -vis de la base de données réelle et prendre le contrôle total de l'environnement de test.
L'utilisation de test doubles offre plusieurs avantages clés :
- Isolation : Ils vous permettent de tester votre unité de code (par exemple, la logique de formatage) en isolation complète de ses dépendances (par exemple, la base de données). Si le test échoue, vous savez que le problème se situe dans l'unité testée, et non ailleurs.
- Vitesse : Remplacer les opérations lentes comme les requêtes réseau ou les requêtes de base de données par un test double en mémoire rend votre suite de tests considérablement plus rapide. Les tests rapides sont exécutés plus souvent, ce qui conduit à une boucle de rétroaction plus courte pour les développeurs.
- Déterminisme : Vous pouvez configurer le test double pour qu'il renvoie des données prévisibles à chaque exécution du test. Cela élimine les tests instables et garantit qu'un test en échec indique un véritable problème.
- Capacité à tester les cas limites : Vous pouvez facilement configurer un double pour simuler des conditions d'erreur, comme lever une
ConnectionErrorou renvoyer des données vides, vous permettant de vérifier que votre code gère ces situations avec élégance.
La Taxonomie des Test Doubles : Au-delĂ des simples "Mocks"
Bien que les développeurs utilisent souvent le terme "mock" génériquement pour désigner tout test double, il est utile de comprendre la terminologie plus précise inventée par Gerard Meszaros dans son livre "xUnit Test Patterns". Connaître ces distinctions vous aide à penser plus clairement à ce que vous essayez d'accomplir dans votre test.
1. Dummy (Factice)
Un objet Dummy est le test double le plus simple. Il est passé pour remplir une liste de paramètres mais n'est jamais réellement utilisé. Ses méthodes ne sont généralement pas appelées. Vous utilisez un dummy lorsque vous devez fournir un argument à une méthode, mais que vous ne vous souciez pas du comportement de cet argument dans le contexte du test spécifique.
Exemple : Si une fonction nécessite un objet 'logger' mais que votre test ne se soucie pas de ce qui est journalisé, vous pourriez passer un objet factice.
2. Fake (Faux)
Un objet Fake a une implémentation fonctionnelle, mais c'est une version beaucoup plus simple de l'objet de production. Il n'utilise pas de ressources externes et substitue une implémentation légère à une implémentation lourde. L'exemple classique est une base de données en mémoire qui remplace une connexion de base de données réelle. Cela fonctionne réellement – vous pouvez y ajouter des données et en lire – mais ce n'est qu'un simple dictionnaire ou une liste en interne.
3. Stub (Bouchon)
Un Stub fournit des réponses préprogrammées, "en conserve", aux appels de méthode effectués pendant un test. Il est utilisé lorsque votre code a besoin de recevoir des données spécifiques d'une dépendance. Par exemple, vous pourriez "bouchonner" une méthode comme api_client.get_user(user_id=123) pour qu'elle renvoie toujours un dictionnaire utilisateur spécifique, sans réellement effectuer un appel API.
4. Spy (Espion)
Un Spy est un stub qui enregistre également des informations sur la façon dont il a été appelé. Par exemple, il pourrait enregistrer le nombre de fois qu'une méthode a été appelée ou les arguments qui lui ont été passés. Cela vous permet d'"espionner" l'interaction entre votre code et sa dépendance, puis de faire des assertions sur cette interaction après coup.
5. Mock (Simulacre)
Un Mock est le type de test double le plus "conscient". C'est un objet préprogrammé avec des attentes sur les méthodes qui seront appelées, avec quels arguments et dans quel ordre. Un test utilisant un objet mock échouera généralement non seulement si le code testé produit un résultat incorrect, mais aussi s'il n'interagit pas avec le mock de la manière précisément attendue. Les mocks sont excellents pour la vérification de comportement – s'assurer qu'une séquence d'actions spécifique s'est produite.
La bibliothèque unittest.mock de Python fournit une classe unique et puissante qui peut agir comme un Stub, un Spy ou un Mock, selon la façon dont vous l'utilisez.
Présentation du poids lourd de Python : La bibliothèque `unittest.mock`
Faisant partie de la bibliothèque standard de Python depuis la version 3.3, unittest.mock est la solution canonique pour créer des test doubles. Sa flexibilité et sa puissance en font un outil essentiel pour tout développeur Python sérieux. Si vous utilisez une version plus ancienne de Python, vous pouvez installer la bibliothèque rétroportée via pip : pip install mock.
Le cœur de la bibliothèque s'articule autour de deux classes clés : Mock et son cousin plus capable, MagicMock. Ces objets sont conçus pour être incroyablement flexibles, créant des attributs et des méthodes à la volée lorsque vous y accédez.
Plongée profonde : Les classes `Mock` et `MagicMock`
L'objet `Mock`
Un objet `Mock` est un caméléon. Vous pouvez en créer un, et il répondra immédiatement à tout accès d'attribut ou appel de méthode, renvoyant un autre objet Mock par défaut. Cela vous permet d'enchaîner facilement les appels pendant la configuration.
# Dans un fichier de test...
from unittest.mock import Mock
# Créer un objet mock
mock_api = Mock()
# L'accès à un attribut le crée et renvoie un autre mock
print(mock_api.users)
# Sortie : <Mock name='mock.users' id='...'>
# L'appel d'une méthode renvoie également un mock par défaut
print(mock_api.users.get(id=1))
# Sortie : <Mock name='mock.users.get()' id='...'>
Ce comportement par défaut n'est pas très utile pour les tests. Le vrai pouvoir vient de la configuration du mock pour qu'il se comporte comme l'objet qu'il remplace.
Configuration des valeurs de retour et des effets secondaires
Vous pouvez indiquer à une méthode mock ce qu'il faut renvoyer en utilisant l'attribut return_value. C'est ainsi que vous créez un Stub.
from unittest.mock import Mock
# Créer un mock pour un service de données
mock_service = Mock()
# Configurer la valeur de retour pour un appel de méthode
mock_service.get_data.return_value = {'id': 1, 'name': 'Test Data'}
# Maintenant, lorsque nous l'appelons, nous obtenons notre valeur configurée
result = mock_service.get_data()
print(result)
# Sortie : {'id': 1, 'name': 'Test Data'}
Pour simuler des erreurs, vous pouvez utiliser l'attribut side_effect. C'est parfait pour tester la gestion des erreurs de votre code.
from unittest.mock import Mock
mock_service = Mock()
# Configurer la méthode pour lever une exception
mock_service.get_data.side_effect = ConnectionError("Failed to connect to service")
# L'appel de la méthode va maintenant lever l'exception
try:
mock_service.get_data()
except ConnectionError as e:
print(e)
# Sortie : Échec de la connexion au service
Méthodes d'assertion pour la vérification
Les objets mock agissent également comme des Spies et des Mocks en enregistrant la façon dont ils sont utilisés. Vous pouvez ensuite utiliser une suite de méthodes d'assertion intégrées pour vérifier ces interactions.
mock_object.method.assert_called(): Affirme que la méthode a été appelée au moins une fois.mock_object.method.assert_called_once(): Affirme que la méthode a été appelée exactement une fois.mock_object.method.assert_called_with(*args, **kwargs): Affirme que la méthode a été appelée en dernier avec les arguments spécifiés.mock_object.method.assert_any_call(*args, **kwargs): Affirme que la méthode a été appelée avec ces arguments à tout moment.mock_object.method.assert_not_called(): Affirme que la méthode n'a jamais été appelée.mock_object.call_count: Une propriété entière qui indique le nombre de fois où la méthode a été appelée.
from unittest.mock import Mock
mock_notifier = Mock()
# Imaginez que c'est notre fonction sous test
def process_and_notify(data, notifier):
if data.get('critical'):
notifier.send_alert(message="Critical event occurred!")
# Cas de test 1 : Données critiques
process_and_notify({'critical': True}, mock_notifier)
mock_notifier.send_alert.assert_called_once_with(message="Critical event occurred!")
# Réinitialiser le mock pour le test suivant
mock_notifier.reset_mock()
# Cas de test 2 : Données non critiques
process_and_notify({'critical': False}, mock_notifier)
mock_notifier.send_alert.assert_not_called()
L'objet `MagicMock`
Un `MagicMock` est une sous-classe de `Mock` avec une différence clé : il a des implémentations par défaut pour la plupart des méthodes "magiques" ou "dunder" de Python (par exemple, __len__, __str__, __iter__). Si vous essayez d'utiliser un `Mock` normal dans un contexte qui nécessite l'une de ces méthodes, vous obtiendrez une erreur.
from unittest.mock import Mock, MagicMock
# Utilisation d'un Mock normal
mock_list = Mock()
try:
len(mock_list)
except TypeError as e:
print(e) # Sortie : L'objet 'Mock' n'a pas de len()
# Utilisation d'un MagicMock
magic_mock_list = MagicMock()
print(len(magic_mock_list)) # Sortie : 0 (par défaut)
# Nous pouvons également configurer la valeur de retour de la méthode magique
magic_mock_list.__len__.return_value = 100
print(len(magic_mock_list)) # Sortie : 100
Règle générale : Commencez avec `MagicMock`. C'est généralement plus sûr et couvre plus de cas d'utilisation, comme les objets mockés qui sont utilisés dans les boucles for (nécessitant __iter__) ou les instructions with (nécessitant __enter__ et __exit__).
Implémentation pratique : Le décorateur et gestionnaire de contexte `patch`
Créer un mock est une chose, mais comment faire en sorte que votre code l'utilise à la place de l'objet réel ? C'est là que `patch` intervient. `patch` est un outil puissant dans `unittest.mock` qui remplace temporairement un objet cible par un mock pendant la durée d'un test.
`@patch` en tant que décorateur
La façon la plus courante d'utiliser `patch` est en tant que décorateur sur votre méthode de test. Vous fournissez le chemin de la chaîne de caractères vers l'objet que vous souhaitez remplacer.
Supposons que nous ayons une fonction qui récupère des données d'une API web en utilisant la populaire bibliothèque `requests` :
# dans le fichier : my_app/data_fetcher.py
import requests
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status() # Lever une exception pour les codes d'état incorrects
return response.json()
Nous voulons tester cette fonction sans effectuer un véritable appel réseau. Nous pouvons patcher `requests.get` :
# dans le fichier : tests/test_data_fetcher.py
import unittest
from unittest.mock import patch, Mock
from my_app.data_fetcher import get_user_data
class TestDataFetcher(unittest.TestCase):
@patch('my_app.data_fetcher.requests.get')
def test_get_user_data_success(self, mock_get):
"""Tester la récupération réussie des données."""
# Configurer le mock pour simuler une réponse API réussie
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'John Doe'}
mock_response.raise_for_status.return_value = None # Ne rien faire en cas de succès
mock_get.return_value = mock_response
# Appeler notre fonction
user_data = get_user_data(1)
# Affirmer que notre fonction a effectué l'appel API correct
mock_get.assert_called_once_with('https://api.example.com/users/1')
# Affirmer que notre fonction a retourné les données attendues
self.assertEqual(user_data, {'id': 1, 'name': 'John Doe'})
Notez comment `patch` crée un `MagicMock` et le transmet à notre méthode de test en tant qu'argument `mock_get`. Dans le test, tout appel à `requests.get` à l'intérieur de `my_app.data_fetcher` est redirigé vers notre objet mock.
`patch` en tant que gestionnaire de contexte
Parfois, vous n'avez besoin de patcher quelque chose que pour une petite partie d'un test. L'utilisation de `patch` en tant que gestionnaire de contexte avec une instruction `with` est parfaite pour cela.
# dans le fichier : tests/test_data_fetcher.py
import unittest
from unittest.mock import patch, Mock
from my_app.data_fetcher import get_user_data
class TestDataFetcher(unittest.TestCase):
def test_get_user_data_with_context_manager(self):
"""Tester l'utilisation de patch comme gestionnaire de contexte."""
with patch('my_app.data_fetcher.requests.get') as mock_get:
# Configurer le mock à l'intérieur du bloc 'with'
mock_response = Mock()
mock_response.json.return_value = {'id': 2, 'name': 'Jane Doe'}
mock_get.return_value = mock_response
user_data = get_user_data(2)
mock_get.assert_called_once_with('https://api.example.com/users/2')
self.assertEqual(user_data, {'id': 2, 'name': 'Jane Doe'})
# En dehors du bloc 'with', requests.get est revenu à son état original
Un concept crucial : OĂą patcher ?
C'est la source la plus courante de confusion lors de l'utilisation de `patch`. La règle est la suivante : Vous devez patcher l'objet là où il est recherché, et non là où il est défini.
Illustrons avec un exemple. Supposons que nous ayons deux fichiers :
# dans le fichier : services.py
class Database:
def connect(self):
# ... logique de connexion complexe ...
return "REAL_CONNECTION"
# dans le fichier : main_app.py
from services import Database
def start_app():
db = Database()
connection = db.connect()
print(f"Got connection: {connection}")
return connection
Maintenant, nous voulons tester `start_app` dans `main_app.py` sans créer un véritable objet `Database`. Une erreur courante est d'essayer de patcher `services.Database`.
# dans le fichier : test_main_app.py
import unittest
from unittest.mock import patch
from main_app import start_app
class TestApp(unittest.TestCase):
# C'EST LA MAUVAISE FAÇON DE PATCHER !
@patch('services.Database')
def test_start_app_incorrectly(self, mock_db):
start_app()
# Ce test utilisera toujours la VRAIE classe Database !
# C'EST LA BONNE FAÇON DE PATCHER !
@patch('main_app.Database')
def test_start_app_correctly(self, mock_db_class):
# Nous patchons 'Database' dans l'espace de noms 'main_app'
# Configurer l'instance de mock qui sera créée
mock_instance = mock_db_class.return_value
mock_instance.connect.return_value = "MOCKED_CONNECTION"
connection = start_app()
# Affirmer que notre mock a été utilisé
mock_db_class.assert_called_once() # La classe a-t-elle été instanciée ?
mock_instance.connect.assert_called_once() # La méthode connect a-t-elle été appelée ?
self.assertEqual(connection, "MOCKED_CONNECTION")
Pourquoi le premier test échoue-t-il ? Parce que `main_app.py` exécute `from services import Database`. Cela importe la classe `Database` dans l'espace de noms du module `main_app`. Lorsque `start_app` s'exécute, il recherche `Database` dans son propre module (`main_app`). Patcher `services.Database` le modifie dans le module `services`, mais `main_app` a déjà sa propre référence à la classe originale. L'approche correcte consiste à patcher `main_app.Database`, qui est le nom que le code testé utilise réellement.
Techniques de Mocking Avancées
`spec` et `autospec` : Rendre les Mocks plus sûrs
Un `MagicMock` standard a un inconvénient potentiel : il vous permettra d'appeler n'importe quelle méthode avec n'importe quels arguments, même si cette méthode n'existe pas sur l'objet réel. Cela peut conduire à des tests qui passent mais cachent de vrais problèmes, comme des fautes de frappe dans les noms de méthode ou des changements dans l'API d'un objet réel.
# Classe réelle
class Notifier:
def send_message(self, text):
# ... envoie le message ...
pass
# Un test avec une faute de frappe
from unittest.mock import MagicMock
mock_notifier = MagicMock()
# Oups, une faute de frappe ! La vraie méthode est send_message
mock_notifier.send_mesage("hello") # Aucune erreur n'est levée !
mock_notifier.send_mesage.assert_called_with("hello") # Cette assertion passe !
# Notre test est vert, mais le code de production échouerait.
Pour éviter cela, `unittest.mock` fournit les arguments `spec` et `autospec`.
- `spec=SomeClass` : Cela configure le mock pour avoir la même API que `SomeClass`. Si vous essayez d'accéder à une méthode ou un attribut qui n'existe pas sur la classe réelle, une `AttributeError` sera levée.
- `autospec=True` (ou `autospec=SomeClass`) : C'est encore plus puissant. Il agit comme `spec`, mais il vérifie également la signature d'appel de toutes les méthodes mockées. Si vous appelez une méthode avec un nombre ou des noms d'arguments incorrects, il lèvera une `TypeError`, tout comme l'objet réel le ferait.
from unittest.mock import create_autospec
# Créer un mock qui a la même interface que notre classe Notifier
spec_notifier = create_autospec(Notifier)
try:
# Cela échouera immédiatement à cause de la faute de frappe
spec_notifier.send_mesage("hello")
except AttributeError as e:
print(e) # Sortie : L'objet Mock n'a pas d'attribut 'send_mesage'
try:
# Cela échouera car la signature est incorrecte (pas de mot-clé 'text')
spec_notifier.send_message("hello")
except TypeError as e:
print(e) # Sortie : argument requis manquant : 'text'
# C'est la bonne façon de l'appeler
spec_notifier.send_message(text="hello") # Ça marche !
spec_notifier.send_message.assert_called_once_with(text="hello")
Meilleure pratique : Utilisez toujours `autospec=True` lors du patchage. Cela rend vos tests plus robustes et moins fragiles. `@patch('path.to.thing', autospec=True)`.
Exemple Concret : Tester un service de traitement de données
Regroupons tout avec un exemple plus complet. Nous avons un `ReportGenerator` qui dépend d'une base de données et d'un système de fichiers.
# dans le fichier : app/services.py
class DatabaseConnector:
def get_sales_data(self, start_date, end_date):
# En réalité, cela interrogerait une base de données
raise NotImplementedError("This should not be called in tests")
class FileSaver:
def save_report(self, path, content):
# En réalité, cela écrirait dans un fichier
raise NotImplementedError("This should not be called in tests")
# dans le fichier : app/reports.py
from .services import DatabaseConnector, FileSaver
class ReportGenerator:
def __init__(self):
self.db_connector = DatabaseConnector()
self.file_saver = FileSaver()
def generate_sales_report(self, start_date, end_date, output_path):
"""Fetches sales data and saves a formatted report."""
raw_data = self.db_connector.get_sales_data(start_date, end_date)
if not raw_data:
report_content = "No sales data for this period."
else:
total_sales = sum(item['amount'] for item in raw_data)
report_content = f"Total Sales from {start_date} to {end_date}: ${total_sales:.2f}"
self.file_saver.save_report(path=output_path, content=report_content)
return True
Maintenant, écrivons un test unitaire pour `ReportGenerator.generate_sales_report` qui "mocke" ses dépendances.
# dans le fichier : tests/test_reports.py
import unittest
from datetime import date
from unittest.mock import patch, Mock
from app.reports import ReportGenerator
class TestReportGenerator(unittest.TestCase):
@patch('app.reports.FileSaver', autospec=True)
@patch('app.reports.DatabaseConnector', autospec=True)
def test_generate_sales_report_with_data(self, mock_db_connector_class, mock_file_saver_class):
"""Tester la génération de rapport lorsque la base de données renvoie des données."""
# Organiser : Configurer nos mocks
mock_db_instance = mock_db_connector_class.return_value
mock_file_saver_instance = mock_file_saver_class.return_value
# Configurer le mock de la base de données pour renvoyer des données fictives (Stub)
fake_data = [
{'id': 1, 'amount': 100.50},
{'id': 2, 'amount': 75.00},
{'id': 3, 'amount': 25.25}
]
mock_db_instance.get_sales_data.return_value = fake_data
start = date(2023, 1, 1)
end = date(2023, 1, 31)
path = '/reports/sales_jan_2023.txt'
# Agir : Créer une instance de notre classe et appeler la méthode
generator = ReportGenerator()
result = generator.generate_sales_report(start, end, path)
# Affirmer : Vérifier les interactions et les résultats
# 1. La base de données a-t-elle été appelée correctement ?
mock_db_instance.get_sales_data.assert_called_once_with(start, end)
# 2. Le sauveur de fichier a-t-il été appelé avec le contenu correct et calculé ?
expected_content = "Total Sales from 2023-01-01 to 2023-01-31: $200.75"
mock_file_saver_instance.save_report.assert_called_once_with(
path=path,
content=expected_content
)
# 3. Notre méthode a-t-elle renvoyé la bonne valeur ?
self.assertTrue(result)
Ce test isole parfaitement la logique de `generate_sales_report` des complexités de la base de données et du système de fichiers, tout en vérifiant qu'il interagit correctement avec eux.
Meilleures pratiques pour un "mocking" efficace
- Gardez les Mocks Simples : Un test qui nécessite une configuration de mock très complexe est souvent le signe (une "odeur de test") que l'unité testée est trop complexe et pourrait violer le Principe de Responsabilité Unique. Envisagez de refactoriser le code de production.
- Mockez les Collaborateurs, Pas Tout : Vous ne devez "mocker" que les objets avec lesquels votre unité testée communique (ses collaborateurs). Ne "mockez" pas l'objet que vous testez lui-même.
- Préférez `autospec=True` : Comme mentionné, cela rend vos tests plus robustes en garantissant que l'interface du mock correspond à l'interface de l'objet réel. Cela aide à détecter les problèmes causés par la refactorisation.
- Un Mock par Test (Idéalement) : Un bon test unitaire se concentre sur un seul comportement ou une seule interaction. Si vous vous retrouvez à "mocker" de nombreux objets différents dans un seul test, il pourrait être préférable de le diviser en plusieurs tests plus ciblés.
- Soyez Spécifique dans Vos Assertions : Ne vous contentez pas de vérifier `mock.method.assert_called()`. Utilisez `assert_called_with(...)` pour vous assurer que l'interaction s'est produite avec les données correctes. Cela rend vos tests plus précieux.
- Vos Tests Sont de la Documentation : Utilisez des noms clairs et descriptifs pour vos tests et objets mock (par exemple, `mock_api_client`, `test_login_fails_on_network_error`). Cela rend l'objectif du test clair pour les autres développeurs.
Conclusion
Les test doubles ne sont pas seulement un outil de test ; ils sont une partie fondamentale de la conception de logiciels testables, modulaires et maintenables. En remplaçant les dépendances réelles par des substituts contrôlés, vous pouvez créer une suite de tests rapide, fiable et capable de vérifier chaque recoin de la logique de votre application.
La bibliothèque unittest.mock de Python fournit une boîte à outils de classe mondiale pour implémenter ces modèles. En maîtrisant `MagicMock`, `patch` et la sécurité de `autospec`, vous débloquez la capacité d'écrire des tests unitaires véritablement isolés. Cela vous permet de construire des applications complexes en toute confiance, sachant que vous disposez d'un filet de sécurité de tests précis et ciblés pour détecter les régressions et valider les nouvelles fonctionnalités. Alors, allez-y, commencez à "patcher" et construisez des applications Python plus robustes dès aujourd'hui.