Les tests sont une partie essentielle du développement de logiciels. Ils permettent de détecter les bogues à un stade précoce et réduisent la probabilité d’erreurs en aval.
Pytest est l’un des frameworks de test les plus populaires pour Python. Il vous permet d’écrire de petits tests lisibles qui peuvent s’adapter à la croissance de votre application. Apprenez à configurer et à utiliser Pytest avec votre code Python.
Configuration de Pytest
Avant d’installer Pytest, il est préférable de créer un environnement virtuel pour isoler votre environnement de test, afin d’éviter les conflits avec d’autres paquets et dépendances.
Pour créer un environnement virtuel, exécutez la commande suivante avant d’installer Pytest.
python -m venv tests
Cela créera un nouvel environnement virtuel nommé tests dans votre répertoire actuel. Pour activer l’environnement, exécutez la commande suivante si vous êtes sous Linux ou Mac :
source tests/bin/activate
Pour Windows, exécutez cette commande :
tests\\Scripts\\activate
Pour installer Pytest, vous pouvez utiliser pip, le gestionnaire de paquets Python, avec cette commande dans votre terminal :
pip install pytest
Si vous n’avez pas Pip, ne vous inquiétez pas ; vous pouvez installer Pip sur Windows, Mac et Linux.
Exécutez la commande suivante pour vérifier si vous avez installé Pytest correctement.
pytest --version
Vous devriez obtenir le numéro de la version installée.
Création de votre premier test
Considérons la fonction suivante qui additionne deux nombres et renvoie le résultat.
def add_numbers(a, b):
return a + b
Plusieurs problèmes peuvent survenir avec cette fonction. Par exemple, imaginez ce qui se passe si vous appelez la fonction avec des valeurs non numériques telles que None ou une valeur de type string. Il s’agit là de quelques-uns des cas limites potentiels qui peuvent entraîner l’échec de la fonction.
L’un des premiers tests que vous écrivez doit vérifier si la fonction renvoie le résultat attendu. Pour ce faire, vous pouvez utiliser le mot-clé assert pour comparer la sortie réelle de la fonction à la sortie attendue. Dans le cas de la fonction add_numbers, la fonction de test pourrait ressembler à ceci :
def test_add_numbers():
assert add_numbers(2, 3) == 5
assert add_numbers(-1, 1) == 0
assert add_numbers(0, 0) == 0
Cette fonction de test comprend trois instructions assert, chacune d’entre elles comparant la sortie de la fonction add_numbers à une valeur attendue. Le premier test vérifie que l’addition de 2 et de 3 donne 5, le deuxième que l’addition de -1 et de 1 donne 0 et le troisième que l’addition de 0 et de 0 donne 0.
Comment exécuter des tests avec Pytest
Après avoir écrit vos tests, l’étape suivante consiste à les exécuter. Pour ce faire avec Pytest, naviguez jusqu’au répertoire contenant votre fichier de test et exécutez la commande pytest :
pytest
Si tout fonctionne comme prévu, vous verrez un message indiquant que tous les tests ont été passés avec succès. Cependant, si l’une des assertions échoue, Pytest signalera une erreur et vous indiquera les valeurs d’entrée à l’origine de l’échec.
Par exemple, supposons que vous exécutiez la fonction de test suivante pour la fonction add_numbers :
def test_add_numbers():
assert add_numbers(2, 3) == 6
assert add_numbers(-1, 1) == 0
assert add_numbers(0, 0) == 0
La première assertion échouera parce que la valeur attendue était 6, mais la valeur réelle était 5 (la somme de 2 et 3). Pytest renverra le message suivant :
Ce message vous indique les valeurs d’entrée à l’origine de la valeur et vous indique également quelle devrait être la valeur réelle. Il est ainsi facile d’identifier et de corriger rapidement les erreurs dans votre code.
Utiliser Pytest.raises pour affirmer des exceptions
Maintenant, écrivons un test pour couvrir l’un des cas limites de la fonction add_numbers. Lorsque vous passez un argument non numérique comme None à la fonction, Python devrait lever une exception TypeError.
Vous devriez déjà gérer des exceptions dans vos programmes Python, et vous pouvez également tester que votre code les lève correctement.
Pour ce faire, copiez la fonction de test suivante dans votre fichier. Elle utilise le gestionnaire de contexte pytest.raises pour vérifier si l’appel à la fonction add_number avec « None » soulève une exception TypeError.
import pytest
def test_add_numbers_with_invalid_inputs():
with pytest.raises(TypeError):
add_numbers(None, 2)
Exécutez ensuite Pytest à partir de la ligne de commande. Si l’exception n’est pas levée, le test échoue.
Vous pouvez aller plus loin et vérifier les détails du message d’exception. Le gestionnaire de contexte produit un objet ExceptionInfo contenant les détails.
Par exemple, dans cette fonction de test, affirmez le message d’exception comme suit :
def test_add_numbers_with_invalid_inputs():
with pytest.raises(TypeError) as exc_info:
add_numbers(None, 2)
assert exc_info.value.args[0] == "unsupported operand type(s) for +: 'NoneType' and 'int'"
Si le message ne correspond pas à celui du test, Pytest indiquera un échec.
Comment utiliser les tests paramétrés pour tester plusieurs entrées à la fois ?
Au lieu d’appeler manuellement une fonction avec plusieurs entrées comme ceci :
def test_add_numbers():
assert add_numbers(2, 3) == 6
assert add_numbers(-1, 1) == 0
assert add_numbers(0, 0) == 0
Pytest fournit une fonction de test paramétrée qui vous permet de faire la même chose plus facilement. Voici comment réécrire la fonction de test ci-dessus :
import pytest
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0)
])
def test_add_numbers(a, b, expected):
assert add_numbers(a, b) == expected
Comment exécuter plusieurs tests
Jusqu’à présent, vous n’avez écrit que deux tests pour la fonction add_numbers. Pour les fonctions plus complexes comportant plus de tests, vous pouvez les regrouper dans une classe.
Par exemple, voici comment créer une classe de test pour la fonction add.
class TestAddFunction:
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0),
])
def test_addition_with_numbers(self, a, b, expected):
assert add_numbers(a, b) == expected
def test_add_numbers_with_invalid_inputs(self):
with pytest.raises(TypeError) as exc_info:
add_numbers(None, 2)
assert exc_info.value.args[0] == "unsupported operand type(s) for +: 'NoneType' and 'int'"
Notez que vous devez préfixer le nom de la classe par « Test » pour que Pytest puisse l’identifier comme une classe de test et l’exécuter.
Pytest a beaucoup plus de fonctionnalités
En utilisant Pytest, vous pouvez automatiquement vérifier que votre code fonctionne comme vous le souhaitez. Pytest offre de nombreuses autres fonctionnalités telles que les fixtures qui vous permettent de mettre en place et de supprimer les données de test et les marques pour la mise en place de métadonnées sur vos fonctions de test.
En outre, vous pouvez intégrer Pytest dans votre pipeline CI et commencer à exécuter des tests automatiquement et en continu lorsque vous modifiez votre code.