Module unittest de Python

Beginner

This tutorial is from open-source community. Access the source code

Introduction

Dans ce laboratoire, vous apprendrez à utiliser le module intégré unittest de Python. Ce module propose un cadre pour organiser et exécuter des tests, ce qui est une partie essentielle du développement logiciel pour garantir que votre code fonctionne correctement.

Vous apprendrez également à créer et exécuter des cas de test de base, ainsi qu'à tester les erreurs et exceptions attendues. Le module unittest simplifie le processus de création de suites de tests, d'exécution des tests et de vérification des résultats. Le fichier teststock.py sera créé au cours de ce laboratoire.

Création de votre premier test unitaire

Le module unittest de Python est un outil puissant qui propose une manière structurée d'organiser et d'exécuter des tests. Avant de plonger dans l'écriture de notre premier test unitaire, comprenons quelques concepts clés. Les fixtures de test (test fixtures) sont des méthodes telles que setUp et tearDown qui aident à préparer l'environnement avant un test et à le nettoyer ensuite. Les cas de test (test cases) sont des unités individuelles de test, les suites de test (test suites) sont des collections de cas de test, et les exécuteurs de test (test runners) sont chargés d'exécuter ces tests et de présenter les résultats.

Dans cette première étape, nous allons créer un fichier de test de base pour la classe Stock, qui est déjà définie dans le fichier stock.py.

  1. Tout d'abord, ouvrons le fichier stock.py. Cela nous aidera à comprendre la classe Stock que nous allons tester. En regardant le code dans stock.py, nous pouvons voir comment la classe est structurée, quelles sont ses attributs et quelles méthodes elle propose. Pour afficher le contenu du fichier stock.py, exécutez la commande suivante dans votre terminal :
cat stock.py
  1. Maintenant, il est temps de créer un nouveau fichier nommé teststock.py en utilisant votre éditeur de texte préféré. Ce fichier contiendra nos cas de test pour la classe Stock. Voici le code que vous devez écrire dans le fichier teststock.py :
## teststock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

if __name__ == '__main__':
    unittest.main()

Décortiquons les éléments clés de ce code :

  • import unittest : Cette ligne importe le module unittest, qui fournit les outils et les classes nécessaires pour écrire et exécuter des tests en Python.
  • import stock : Cela importe le module qui contient notre classe Stock. Sans cette importation, nous ne pourrions pas accéder à la classe Stock dans notre code de test.
  • class TestStock(unittest.TestCase) : Nous créons une nouvelle classe nommée TestStock qui hérite de unittest.TestCase. Cela fait de notre classe TestStock une classe de cas de test, qui peut contenir plusieurs méthodes de test.
  • def test_create(self) : Il s'agit d'une méthode de test. Dans le cadre (framework) unittest, toutes les méthodes de test doivent commencer par le préfixe test_. Cette méthode crée une instance de la classe Stock, puis utilise la méthode assertEqual pour vérifier si les attributs de l'instance Stock correspondent aux valeurs attendues.
  • assertEqual : C'est une méthode fournie par la classe TestCase. Elle vérifie si deux valeurs sont égales. Si elles ne sont pas égales, le test échouera.
  • unittest.main() : Lorsque ce script est exécuté directement, unittest.main() exécutera toutes les méthodes de test de la classe TestStock et affichera les résultats.
  1. Après avoir écrit le code dans le fichier teststock.py, enregistrez - le. Ensuite, exécutez la commande suivante dans votre terminal pour exécuter le test :
python3 teststock.py

Vous devriez voir une sortie similaire à ceci :

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Le point unique (.) dans la sortie indique qu'un test a réussi avec succès. Si un test échoue, vous verrez un F à la place du point, ainsi que des informations détaillées sur ce qui a mal fonctionné dans le test. Cette sortie vous aide à identifier rapidement si votre code fonctionne comme prévu ou s'il y a des problèmes à corriger.

Élargissement de vos cas de test

Maintenant que vous avez créé un cas de test de base, il est temps d'élargir votre champ de test. Ajouter plus de tests vous aidera à couvrir la fonctionnalité restante de la classe Stock. De cette façon, vous pouvez vous assurer que tous les aspects de la classe fonctionnent comme prévu. Nous allons modifier la classe TestStock pour inclure des tests pour plusieurs méthodes et propriétés.

  1. Ouvrez le fichier teststock.py. À l'intérieur de la classe TestStock, nous allons ajouter quelques nouvelles méthodes de test. Ces méthodes testeront différentes parties de la classe Stock. Voici le code que vous devez ajouter :
def test_create_keyword_args(self):
    s = stock.Stock(name='GOOG', shares=100, price=490.1)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_cost(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s.cost, 49010.0)

def test_sell(self):
    s = stock.Stock('GOOG', 100, 490.1)
    s.sell(20)
    self.assertEqual(s.shares, 80)

def test_from_row(self):
    row = ['GOOG', '100', '490.1']
    s = stock.Stock.from_row(row)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_repr(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)")

def test_eq(self):
    s1 = stock.Stock('GOOG', 100, 490.1)
    s2 = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s1, s2)

Examinons de plus près ce que chaque test fait :

  • test_create_keyword_args : Ce test vérifie si vous pouvez créer un objet Stock en utilisant des arguments nommés (keyword arguments). Il vérifie que les attributs de l'objet sont correctement définis.
  • test_cost : Ce test vérifie si la propriété cost d'un objet Stock renvoie la bonne valeur, qui est calculée comme le nombre d'actions multiplié par le prix.
  • test_sell : Ce test vérifie si la méthode sell() d'un objet Stock met correctement à jour le nombre d'actions après avoir vendu certaines d'entre elles.
  • test_from_row : Ce test vérifie si la méthode de classe from_row() peut créer une nouvelle instance de Stock à partir d'une ligne de données.
  • test_repr : Ce test vérifie si la méthode __repr__() d'un objet Stock renvoie la représentation sous forme de chaîne de caractères attendue.
  • test_eq : Ce test vérifie si la méthode __eq__() compare correctement deux objets Stock pour voir s'ils sont égaux.
  1. Après avoir ajouté ces méthodes de test, enregistrez le fichier teststock.py. Ensuite, exécutez les tests à nouveau en utilisant la commande suivante dans votre terminal :
python3 teststock.py

Si tous les tests réussissent, vous devriez voir une sortie comme celle - ci :

......
----------------------------------------------------------------------
Ran 7 tests in 0.001s

OK

Les sept points dans la sortie représentent chaque test. Chaque point indique qu'un test a réussi avec succès. Donc, si vous voyez sept points, cela signifie que les sept tests ont réussi.

Test des exceptions

Le test est une partie cruciale du développement logiciel, et un aspect important consiste à s'assurer que votre code peut gérer correctement les conditions d'erreur. En Python, le module unittest offre un moyen pratique de tester si des exceptions spécifiques sont levées comme prévu.

  1. Ouvrez le fichier teststock.py. Nous allons ajouter quelques méthodes de test conçues pour vérifier les exceptions. Ces tests nous aideront à nous assurer que notre code se comporte correctement lorsqu'il rencontre des entrées invalides.
def test_shares_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.shares = '50'

def test_shares_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.shares = -50

def test_price_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.price = '490.1'

def test_price_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.price = -490.1

def test_attribute_error(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(AttributeError):
        s.share = 100  ## 'share' is incorrect, should be 'shares'

Comprenons maintenant comment fonctionnent ces tests d'exceptions.

  • La déclaration with self.assertRaises(ExceptionType): crée un gestionnaire de contexte (context manager). Ce gestionnaire de contexte vérifie si le code à l'intérieur du bloc with lève l'exception spécifiée.
  • Si l'exception attendue est levée à l'intérieur du bloc with, le test réussit. Cela signifie que notre code détecte correctement l'entrée invalide et lève l'erreur appropriée.
  • Si aucune exception n'est levée ou si une autre exception est levée, le test échoue. Cela indique que notre code peut ne pas gérer l'entrée invalide comme prévu.

Ces tests sont conçus pour vérifier les scénarios suivants :

  • Définir l'attribut shares sur une chaîne de caractères devrait lever une TypeError car shares devrait être un nombre.
  • Définir l'attribut shares sur un nombre négatif devrait lever une ValueError car le nombre d'actions ne peut pas être négatif.
  • Définir l'attribut price sur une chaîne de caractères devrait lever une TypeError car price devrait être un nombre.
  • Définir l'attribut price sur un nombre négatif devrait lever une ValueError car le prix ne peut pas être négatif.
  • Tenter de définir un attribut inexistant share (notez l'absence de 's') devrait lever une AttributeError car le nom correct de l'attribut est shares.
  1. Après avoir ajouté ces méthodes de test, enregistrez le fichier teststock.py. Ensuite, exécutez tous les tests en utilisant la commande suivante dans votre terminal :
python3 teststock.py

Si tout fonctionne correctement, vous devriez voir une sortie indiquant que les 12 tests ont réussi. La sortie ressemblera à ceci :

............
----------------------------------------------------------------------
Ran 12 tests in 0.002s

OK

Les douze points représentent tous les tests que vous avez écrits jusqu'à présent. Il y avait 7 tests à l'étape précédente, et nous venons d'en ajouter 5 de nouveaux. Cette sortie montre que votre code gère les exceptions comme prévu, ce qui est un excellent signe d'un programme bien testé.

Exécution de tests sélectionnés et utilisation de la découverte de tests

Le module unittest en Python est un outil puissant qui vous permet de tester efficacement votre code. Il propose plusieurs méthodes pour exécuter des tests spécifiques ou découvrir et exécuter automatiquement tous les tests de votre projet. Cela est très utile car il vous aide à vous concentrer sur des parties spécifiques de votre code lors des tests ou à vérifier rapidement l'ensemble des tests du projet.

Exécution de tests spécifiques

Parfois, vous pouvez souhaiter exécuter seulement des méthodes de test ou des classes de test spécifiques au lieu de l'ensemble des tests. Vous pouvez y parvenir en utilisant l'option de modèle (pattern) avec le module unittest. Cela vous donne plus de contrôle sur les tests qui sont exécutés, ce qui peut être pratique lorsque vous déboguez une partie particulière de votre code.

  1. Pour exécuter seulement les tests liés à la création d'un objet Stock :
python3 -m unittest teststock.TestStock.test_create

Dans cette commande, python3 -m unittest indique à Python d'exécuter le module unittest. teststock est le nom du fichier de test, TestStock est le nom de la classe de test, et test_create est la méthode de test spécifique que nous voulons exécuter. En exécutant cette commande, vous pouvez rapidement vérifier si le code lié à la création d'un objet Stock fonctionne comme prévu.

  1. Pour exécuter tous les tests de la classe TestStock :
python3 -m unittest teststock.TestStock

Ici, nous omettons le nom de la méthode de test spécifique. Ainsi, cette commande exécutera toutes les méthodes de test de la classe TestStock dans le fichier teststock. Cela est utile lorsque vous voulez vérifier la fonctionnalité globale des cas de test de l'objet Stock.

Utilisation de la découverte de tests

Le module unittest peut découvrir et exécuter automatiquement tous les fichiers de test de votre projet. Cela vous évite le détail de spécifier manuellement chaque fichier de test à exécuter, en particulier dans les projets plus grands avec de nombreux fichiers de test.

  1. Renommez le fichier actuel pour qu'il suive le modèle de nommage de la découverte de tests :
mv teststock.py test_stock.py

Le mécanisme de découverte de tests dans unittest recherche les fichiers qui suivent le modèle de nommage test_*.py. En renommant le fichier en test_stock.py, nous facilitons la tâche du module unittest pour trouver et exécuter les tests de ce fichier.

  1. Exécutez la découverte de tests :
python3 -m unittest discover

Cette commande indique au module unittest de découvrir et d'exécuter automatiquement tous les fichiers de test qui correspondent au modèle test_*.py dans le répertoire actuel. Il parcourra le répertoire et exécutera tous les cas de test trouvés dans les fichiers correspondants.

  1. Vous pouvez également spécifier un répertoire dans lequel rechercher les tests :
python3 -m unittest discover -s . -p "test_*.py"

Où :

  • -s . spécifie le répertoire de départ pour la découverte (le répertoire actuel dans ce cas). Le point (.) représente le répertoire actuel. Vous pouvez le changer pour un autre chemin de répertoire si vous voulez rechercher des tests dans un autre emplacement.
  • -p "test_*.py" est le modèle pour correspondre aux fichiers de test. Cela garantit que seuls les fichiers dont le nom commence par test_ et ayant l'extension .py sont considérés comme des fichiers de test.

Vous devriez voir tous les 12 tests s'exécuter et réussir, comme précédemment.

  1. Renommez le fichier en son nom d'origine pour assurer la cohérence avec le laboratoire :
mv test_stock.py teststock.py

Après avoir exécuté la découverte de tests, nous renommons le fichier en son nom d'origine pour maintenir la cohérence de l'environnement de laboratoire.

En utilisant la découverte de tests, vous pouvez facilement exécuter tous les tests d'un projet sans avoir à spécifier chaque fichier de test individuellement. Cela rend le processus de test plus efficace et moins sujet aux erreurs.

Résumé

Dans ce laboratoire, vous avez appris à utiliser le module unittest de Python pour créer et exécuter des tests automatisés. Vous avez créé un cas de test de base en étendant la classe unittest.TestCase, écrit des tests pour vérifier le bon fonctionnement des méthodes et propriétés d'une classe, et créé des tests pour vérifier que les exceptions appropriées sont levées en cas d'erreur. Vous avez également appris à exécuter des tests spécifiques et à utiliser la découverte de tests.

Le test unitaire est une compétence fondamentale dans le développement logiciel, assurant la fiabilité et la correction du code. Écrire des tests approfondis permet de détecter les bogues tôt et donne confiance dans le comportement de votre code. Lorsque vous développez des applications Python, envisagez d'adopter une approche de développement piloté par les tests (Test-Driven Development - TDD), en écrivant les tests avant d'implémenter la fonctionnalité pour obtenir un code plus robuste et plus maintenable.