Recherche de site Web

Une introduction douce aux tests unitaires en Python


Les tests unitaires sont une méthode de test de logiciels qui examine les plus petits morceaux de code testables, appelés unités, dont le bon fonctionnement est testé. En effectuant des tests unitaires, nous pouvons vérifier que chaque partie du code, y compris les fonctions d'assistance qui ne peuvent pas être exposées à l'utilisateur, fonctionne correctement et comme prévu.

L'idée est que nous vérifions indépendamment chaque petit élément de notre programme pour nous assurer qu'il fonctionne. Cela contraste avec les tests de régression et d'intégration, qui testent que les différentes parties du programme fonctionnent bien ensemble et comme prévu.

Dans cet article, vous découvrirez comment implémenter des tests unitaires en Python à l'aide de deux frameworks de tests unitaires populaires : le framework PyUnit intégré et le framework PyTest.

Après avoir terminé ce tutoriel, vous saurez :

  • Bibliothèques de tests unitaires en Python telles que PyUnit et PyTest
  • Vérification du comportement attendu des fonctions grâce à l'utilisation de tests unitaires

Démarrez votre projet avec mon nouveau livre Python pour l'apprentissage automatique, comprenant des tutoriels étape par étape et les fichiers code source Python pour tous exemples.

Aperçu

Le didacticiel est divisé en cinq parties ; ils sont:

  • Que sont les tests unitaires et pourquoi sont-ils importants ?
  • Qu'est-ce que le développement piloté par les tests (TDD) ?
  • Utilisation du framework PyUnit intégré à Python
  • Utiliser la bibliothèque PyTest
  • Les tests unitaires en action

Que sont les tests unitaires et pourquoi sont-ils importants ?

Vous souvenez-vous d'avoir fait des mathématiques à l'école, d'avoir effectué différentes procédures arithmétiques avant de les combiner pour obtenir la bonne réponse ? Imaginez comment vous vérifieriez que les calculs effectués à chaque étape étaient corrects et que vous n’avez commis aucune erreur d’inattention ni rien mal écrit.

Maintenant, étendez cette idée au code ! Nous ne voudrions pas avoir à parcourir constamment notre code pour vérifier statiquement son exactitude, alors comment créeriez-vous un test pour garantir que le morceau de code suivant renvoie réellement l'aire du rectangle ?

def calculate_area_rectangle(width, height):
    return width * height

Nous pourrions exécuter le code avec quelques exemples de tests et voir s'il renvoie le résultat attendu.

C’est l’idée d’un test unitaire ! Un test unitaire est un test qui vérifie un seul composant de code, généralement modularisé en fonction, et garantit qu'il fonctionne comme prévu.

Les tests unitaires sont une partie importante des tests de régression pour garantir que le code fonctionne toujours comme prévu après avoir apporté des modifications au code et contribuent à garantir la stabilité du code. Après avoir apporté des modifications à notre code, nous pouvons exécuter les tests unitaires que nous avons créés précédemment pour garantir que les fonctionnalités existantes dans d'autres parties de la base de code n'ont pas été affectées par nos modifications.

Un autre avantage clé des tests unitaires est qu’ils permettent d’isoler facilement les erreurs. Imaginez exécuter l'intégralité du projet et recevoir une série d'erreurs. Comment procéderions-nous pour déboguer notre code ?

C'est là que les tests unitaires entrent en jeu. Nous pouvons analyser les résultats de nos tests unitaires pour voir si un composant de notre code a généré des erreurs et commencer le débogage à partir de là. Cela ne veut pas dire que les tests unitaires peuvent toujours nous aider à trouver le bogue, mais ils constituent un point de départ beaucoup plus pratique avant de commencer à examiner l'intégration des composants dans les tests d'intégration.

Pour la suite de l'article, nous montrerons comment faire des tests unitaires en testant les fonctions de cette classe Rectangle :

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        return self.width * self.height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

Maintenant que nous avons motivé les tests unitaires, explorons comment exactement nous pouvons utiliser les tests unitaires dans le cadre de notre pipeline de développement et comment les implémenter en Python !

Développement piloté par les tests

Les tests sont si importants pour un bon développement logiciel qu’il existe même un processus de développement logiciel basé sur les tests, le Test Driven Development (TDD). Trois règles de TDD proposées par Robert C. Martin sont :

  • Vous n'êtes pas autorisé à écrire du code de production, sauf si c'est pour réussir un test unitaire qui échoue.
  • Vous n'êtes pas autorisé à écrire plus d'un test unitaire que ce qui est suffisant pour échouer, et les échecs de compilation sont des échecs.
  • Vous n'êtes pas autorisé à écrire plus de code de production que ce qui est suffisant pour réussir le test unitaire ayant échoué.

L'idée clé de TDD est que nous basons notre développement logiciel autour d'un ensemble de tests unitaires que nous avons créés, ce qui fait des tests unitaires le cœur du processus de développement logiciel TDD. De cette façon, vous êtes assuré de disposer d’un test pour chaque composant que vous développez.

TDD privilégie également les tests plus petits, ce qui signifie des tests plus spécifiques et testant moins de composants à la fois. Cela facilite le traçage des erreurs, et les tests plus petits sont également plus faciles à lire et à comprendre car il y a moins de composants en jeu dans une seule exécution.

Cela ne signifie pas que vous devez utiliser TDD pour vos projets. Mais vous pouvez considérer cela comme une méthode pour développer votre code et les tests en même temps.

Utilisation du framework PyUnit intégré à Python

Vous vous demandez peut-être pourquoi avons-nous besoin de frameworks de tests unitaires puisque Python et d'autres langages proposent le mot-clé assert ? Les frameworks de tests unitaires aident à automatiser le processus de test et nous permettent d'exécuter plusieurs tests sur la même fonction avec différents paramètres, de vérifier les exceptions attendues et bien d'autres.

PyUnit est le framework de tests unitaires intégré de Python et la version Python du framework de test JUnit correspondant pour Java. Pour commencer à créer un fichier de test, nous devons importer la bibliothèque unittest pour utiliser PyUnit :

import unittest

Ensuite, nous pouvons commencer à écrire le premier test unitaire. Les tests unitaires dans PyUnit sont structurés en sous-classes de la classe unittest.TestCase, et nous pouvons remplacer la méthode runTest() pour effectuer nos propres tests unitaires qui vérifient les conditions en utilisant différentes assertions. fonctions dans unittest.TestCase :

class TestGetAreaRectangle(unittest.TestCase):
    def runTest(self):
        rectangle = Rectangle(2, 3)
        self.assertEqual(rectangle.get_area(), 6, "incorrect area")

C'est notre premier test unitaire ! Il vérifie si la méthode rectangle.get_area() renvoie la zone correcte pour un rectangle de largeur=2 et de longueur=3. Nous utilisons self.assertEqual au lieu d'utiliser simplement assert pour permettre à la bibliothèque unittest de permettre au programme d'exécution d'accumuler tous les cas de test et de produire un rapport.

L'utilisation des différentes fonctions d'assertion dans unittest.TestCase nous donne également une meilleure capacité à tester différents comportements tels que self.assertRaises(exception). Cela nous permet de vérifier si un certain bloc de code produit une exception attendue.

Pour exécuter le test unitaire, nous appelons unittest.main() dans notre programme,

...
unittest.main()

Puisque le code renvoie le résultat attendu pour ce cas, il renvoie que les tests s'exécutent avec succès, avec le résultat :

.
----------------------------------------------------------------------
Ran 1 test in 0.003s

OK

Le code complet est le suivant :

import unittest

# Our code to be tested
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        return self.width * self.height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

# The test based on unittest module
class TestGetAreaRectangle(unittest.TestCase):
    def runTest(self):
        rectangle = Rectangle(2, 3)
        self.assertEqual(rectangle.get_area(), 6, "incorrect area")

# run the test
unittest.main()

Remarque : Dans ce qui précède, notre classe de logique métier Rectangle et notre code de test TestGetAreaRectangle sont rassemblés. En réalité, vous pouvez les mettre dans des fichiers séparés et importer la logique métier dans votre code de test. Cela peut vous aider à mieux gérer le code.

Nous pouvons également imbriquer plusieurs tests unitaires dans une sous-classe de unittest.TestCase, en nommant les méthodes de la nouvelle sous-classe avec le préfixe « test », par exemple :

class TestGetAreaRectangle(unittest.TestCase):
    def test_normal_case(self):
        rectangle = Rectangle(2, 3)
        self.assertEqual(rectangle.get_area(), 6, "incorrect area")

    def test_negative_case(self): 
        """expect -1 as output to denote error when looking at negative area"""
        rectangle = Rectangle(-1, 2)
        self.assertEqual(rectangle.get_area(), -1, "incorrect negative output")

Exécuter ceci nous donnera notre première erreur :

F.
======================================================================
FAIL: test_negative_case (__main__.TestGetAreaRectangle)
expect -1 as output to denote error when looking at negative area
----------------------------------------------------------------------
Traceback (most recent call last):
 	File "<ipython-input-96-59b1047bb08a>", line 9, in test_negative_case
 		self.assertEqual(rectangle.get_area(), -1, "incorrect negative output")
AssertionError: -2 != -1 : incorrect negative output
----------------------------------------------------------------------
Ran 2 tests in 0.003s

FAILED (failures=1)

Nous pouvons voir le test unitaire qui a échoué, qui est le test_negative_case comme mis en évidence dans la sortie avec le message stderr puisque get_area() ne renvoie pas -1 comme prévu dans notre test.

Il existe de nombreux types différents de fonctions d'assertion définies dans le test unitaire. Par exemple, nous pouvons utiliser la classe TestCase :

def test_geq(self):
  """tests if value is greater than or equal to a particular target"""
  self.assertGreaterEqual(self.rectangle.get_area(), -1)

Nous pouvons même vérifier si une exception particulière a été levée lors de l'exécution :

def test_assert_raises(self): 
  """using assertRaises to detect if an expected error is raised when running a particular block of code"""
  with self.assertRaises(ZeroDivisionError):
    a = 1 / 0

Maintenant, nous envisageons de développer nos tests. Et si nous avions du code que nous devions exécuter pour configurer avant d'exécuter chaque test ? Eh bien, nous pouvons remplacer la méthode setUp dans unittest.TestCase.

class TestGetAreaRectangleWithSetUp(unittest.TestCase):
  def setUp(self):
    self.rectangle = Rectangle(0, 0)

  def test_normal_case(self):
    self.rectangle.set_width(2)
    self.rectangle.set_height(3)
    self.assertEqual(self.rectangle.get_area(), 6, "incorrect area")

  def test_negative_case(self): 
    """expect -1 as output to denote error when looking at negative area"""
    self.rectangle.set_width(-1)
    self.rectangle.set_height(2)
    self.assertEqual(self.rectangle.get_area(), -1, "incorrect negative output")

Dans l'exemple de code ci-dessus, nous avons remplacé la méthode setUp() de unittest.TestCase par notre propre méthode setUp() qui initialise un Objet Rectangle. Cette méthode setUp() est exécutée avant chaque test unitaire et est utile pour éviter la duplication de code lorsque plusieurs tests s'appuient sur le même morceau de code pour configurer le test. Ceci est similaire au décorateur @Before dans JUnit.

De même, il existe une méthode tearDown() que nous pouvons également remplacer pour que le code soit exécuté après chaque test.

Pour exécuter la méthode une seule fois par classe TestCase, nous pouvons également utiliser la méthode setUpClass comme suit :

class TestGetAreaRectangleWithSetUp(unittest.TestCase):
  @classmethod
  def setUpClass(self):
    self.rectangle = Rectangle(0, 0)

Le code ci-dessus n'est exécuté qu'une seule fois par TestCase au lieu d'une fois par test comme c'est le cas avec setUp.

Pour nous aider à organiser les tests et à sélectionner l'ensemble de tests que nous souhaitons exécuter, nous pouvons regrouper les cas de test en suites de tests, ce qui permet de regrouper les tests qui doivent être exécutés ensemble dans un seul objet :

...
# loads all unit tests from TestGetAreaRectangle into a test suite
calculate_area_suite = unittest.TestLoader() \
                       .loadTestsFromTestCase(TestGetAreaRectangleWithSetUp)

Ici, nous introduisons également une autre façon d'exécuter des tests dans PyUnit en utilisant la classe unittest.TextTestRunner, qui nous permet d'exécuter des suites de tests spécifiques.

runner = unittest.TextTestRunner()
runner.run(calculate_area_suite)

Cela donne le même résultat que l'exécution du fichier à partir de la ligne de commande et l'appel de unittest.main().

En rassemblant le tout, voici à quoi ressemblerait le script complet du test unitaire :

class TestGetAreaRectangleWithSetUp(unittest.TestCase):

  @classmethod
  def setUpClass(self):
    #this method is only run once for the entire class rather than being run for each test which is done for setUp()
    self.rectangle = Rectangle(0, 0)

  def test_normal_case(self):
    self.rectangle.set_width(2)
    self.rectangle.set_height(3)
    self.assertEqual(self.rectangle.get_area(), 6, "incorrect area")

  def test_geq(self):
    """tests if value is greater than or equal to a particular target"""
    self.assertGreaterEqual(self.rectangle.get_area(), -1)

  def test_assert_raises(self): 
    """using assertRaises to detect if an expected error is raised when running a particular block of code"""
    with self.assertRaises(ZeroDivisionError):
      a = 1 / 0

Ce n'est que la pointe de l'iceberg de ce que vous pouvez faire avec PyUnit. Nous pouvons également écrire des tests qui recherchent des messages d'exception correspondant à une expression regex ou des méthodes setUp/tearDown qui ne sont exécutées qu'une seule fois - (setUpClass) , Par exemple.

Utiliser PyTest

PyTest est une alternative au module unittest intégré. Pour démarrer avec PyTest, vous devrez d'abord l'installer, ce que vous pouvez faire en utilisant :

pip install pytest

Pour écrire des tests, il vous suffit d'écrire des fonctions avec des noms préfixés par « test » et la procédure de découverte de tests de PyTest pourra trouver vos tests, par exemple :

def test_normal_case(self):
    rectangle = Rectangle(2, 3)
    assert rectangle.get_area() == 6, "incorrect area"

Vous remarquerez que PyTest utilise le mot-clé assert intégré de Python au lieu de son propre ensemble de fonctions d'assertion comme le fait PyUnit, ce qui pourrait le rendre légèrement plus pratique puisque nous pouvons éviter de rechercher les différentes fonctions d'assertion.

Le code complet est le suivant :

# Our code to be tested
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        return self.width * self.height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

# The test function to be executed by PyTest
def test_normal_case():
    rectangle = Rectangle(2, 3)
    assert rectangle.get_area() == 6, "incorrect area"

Après avoir enregistré cela dans un fichier test_file.py, nous pouvons exécuter le test unitaire PyTest en :

python -m pytest test_file.py

Et cela nous donne le résultat :

=================== test session starts ====================
platform darwin -- Python 3.9.9, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/MLM
plugins: anyio-3.4.0, typeguard-2.13.2
collected 1 item

test_file.py .                                       [100%]

==================== 1 passed in 0.01s =====================

Vous remarquerez peut-être que lorsque nous sommes dans PyUnit, nous devons invoquer la routine de test par un exécutant ou en appelant unittest.main(). Mais dans PyTest, nous transmettons simplement le fichier au module. Le module PyTest collectera toutes les fonctions définies avec le préfixe test et les appellera une par une. Et puis il vérifiera si une exception est levée par l'instruction assert. Il peut être plus pratique de permettre aux tests de rester conformes à la logique métier.

PyTest prend également en charge le regroupement de fonctions en classes, mais la classe doit être nommée avec le préfixe « Test » (avec un T majuscule), par exemple :

class TestGetAreaRectangle:
    def test_normal_case(self):
        rectangle = Rectangle(2, 3)
        assert rectangle.get_area() == 6, "incorrect area"
    def test_negative_case(self): 
        """expect -1 as output to denote error when looking at negative area"""
        rectangle = Rectangle(-1, 2)
        assert rectangle.get_area() == -1, "incorrect negative output"

L'exécuter avec PyTest produira le résultat suivant :

=================== test session starts ====================
platform darwin -- Python 3.9.9, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/MLM
plugins: anyio-3.4.0, typeguard-2.13.2
collected 2 items

test_code.py .F                                      [100%]

========================= FAILURES =========================
_________ TestGetAreaRectangle.test_negative_case __________

self = <test_code.TestGetAreaRectangle object at 0x10f5b3fd0>

    def test_negative_case(self):
        """expect -1 as output to denote error when looking at negative area"""
        rectangle = Rectangle(-1, 2)
>       assert rectangle.get_area() == -1, "incorrect negative output"
E       AssertionError: incorrect negative output
E       assert -2 == -1
E        +  where -2 = <bound method Rectangle.get_area of <test_code.Rectangle object at 0x10f5b3df0>>()
E        +    where <bound method Rectangle.get_area of <test_code.Rectangle object at 0x10f5b3df0>> = <test_code.Rectangle object at 0x10f5b3df0>.get_area

unittest5.py:24: AssertionError
================= short test summary info ==================
FAILED test_code.py::TestGetAreaRectangle::test_negative_case
=============== 1 failed, 1 passed in 0.12s ================

Le code complet est le suivant :

# Our code to be tested
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        return self.width * self.height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

# The test functions to be executed by PyTest
class TestGetAreaRectangle:
    def test_normal_case(self):
        rectangle = Rectangle(2, 3)
        assert rectangle.get_area() == 6, "incorrect area"
    def test_negative_case(self):
        """expect -1 as output to denote error when looking at negative area"""
        rectangle = Rectangle(-1, 2)
        assert rectangle.get_area() == -1, "incorrect negative output"

Pour implémenter le code de configuration et de démontage de nos tests, PyTest dispose d'un système de luminaires extrêmement flexible, où les luminaires sont des fonctions qui ont une valeur de retour. Le système d'appareils de PyTest permet le partage d'appareils entre classes, modules, packages ou sessions, ainsi que d'appareils qui peuvent appeler d'autres appareils comme arguments.

Nous incluons ici une introduction simple au système de luminaires de PyTest :

@pytest.fixture
def rectangle():
    return Rectangle(0, 0)

def test_negative_case(rectangle): 
    print (rectangle.width)
    rectangle.set_width(-1)
    rectangle.set_height(2)
    assert rectangle.get_area() == -1, "incorrect negative output"

Le code ci-dessus introduit Rectangle en tant qu'appareil, et PyTest fait correspondre le rectangle dans la liste d'arguments de test_negative_case avec l'appareil et fournit à test_negative_case son propre ensemble de sorties de la fonction rectangle. . Il fait cela pour tous les autres tests. Cependant, notez que les appareils peuvent être demandés plus d'une fois par test et pour chaque test, l'appareil n'est exécuté qu'une seule fois et le résultat est mis en cache. Cela signifie que toutes les références à cet appareil lors de l'exécution d'un test individuel font référence à la même valeur de retour (ce qui est important si la valeur de retour est un type de référence).

Le code complet est le suivant :

import pytest

# Our code to be tested
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        return self.width * self.height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

@pytest.fixture
def rectangle():
    return Rectangle(0, 0)

def test_negative_case(rectangle):
    print (rectangle.width)
    rectangle.set_width(-1)
    rectangle.set_height(2)
    assert rectangle.get_area() == -1, "incorrect negative output"

Comme PyUnit, PyTest possède de nombreuses autres fonctionnalités qui vous permettront de créer des tests unitaires plus complets et avancés.

Tests unitaires en action

Nous allons maintenant explorer les tests unitaires en action. Pour notre exemple, nous allons tester une fonction qui récupère les données boursières de Yahoo Finance à l'aide de pandas_datareader et le ferons dans PyUnit :

import pandas_datareader.data as web

def get_stock_data(ticker):
    """pull data from stooq"""
    df = web.DataReader(ticker, "yahoo")
    return df

Cette fonction obtient les données boursières sur un symbole boursier particulier en explorant le site Web Yahoo Finance et renvoie le DataFrame pandas. Cela peut échouer de plusieurs manières. Par exemple, le lecteur de données peut ne rien renvoyer (si Yahoo Finance est en panne) ou renvoyer un DataFrame avec des colonnes manquantes ou des données manquantes dans les colonnes (si la source a restructuré son site Web). Par conséquent, nous devons fournir plusieurs fonctions de test pour vérifier plusieurs modes de défaillance :

import datetime
import unittest

import pandas as pd
import pandas_datareader.data as web

def get_stock_data(ticker):
    """pull data from stooq"""
    df = web.DataReader(ticker, 'yahoo')
    return df

class TestGetStockData(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        """We only want to pull this data once for each TestCase since it is an expensive operation"""
        self.df = get_stock_data('^DJI')

    def test_columns_present(self):
        """ensures that the expected columns are all present"""
        self.assertIn("Open", self.df.columns)
        self.assertIn("High", self.df.columns)
        self.assertIn("Low", self.df.columns)
        self.assertIn("Close", self.df.columns)
        self.assertIn("Volume", self.df.columns)

    def test_non_empty(self):
        """ensures that there is more than one row of data"""
        self.assertNotEqual(len(self.df.index), 0)

    def test_high_low(self):
        """ensure high and low are the highest and lowest in the same row"""
        ohlc = self.df[["Open","High","Low","Close"]]
        highest = ohlc.max(axis=1)
        lowest = ohlc.min(axis=1)
        self.assertTrue(ohlc.le(highest, axis=0).all(axis=None))
        self.assertTrue(ohlc.ge(lowest, axis=0).all(axis=None))

    def test_most_recent_within_week(self):
        """most recent data was collected within the last week"""
        most_recent_date = pd.to_datetime(self.df.index[-1])
        self.assertLessEqual((datetime.datetime.today() - most_recent_date).days, 7)

unittest.main()

Notre série de tests unitaires ci-dessus vérifie si certaines colonnes sont présentes (test_columns_present), si la trame de données n'est pas vide (test_non_empty), si les valeurs « haut » et « bas » les colonnes sont en réalité le haut et le bas de la même ligne (test_high_low), et si les données les plus récentes du DataFrame remontent aux 7 derniers jours (test_most_recent_within_week).

Imaginez que vous réalisez un projet d'apprentissage automatique qui consomme les données boursières. Disposer d'un cadre de tests unitaires peut vous aider à déterminer si le prétraitement de vos données fonctionne comme prévu.

À l'aide de ces tests unitaires, nous sommes en mesure d'identifier s'il y a eu un changement important dans le résultat de notre fonction, et cela peut faire partie d'un processus d'intégration continue (CI). Nous pouvons également joindre d'autres tests unitaires selon les besoins en fonction de la fonctionnalité dont nous dépendons de cette fonction.

Pour être complet, voici une version équivalente pour PyTest :

import pytest

# scope="class" tears down the fixture only at the end of the last test in the class, so we avoid rerunning this step.
@pytest.fixture(scope="class")
def stock_df():
  # We only want to pull this data once for each TestCase since it is an expensive operation
  df = get_stock_data('^DJI')
  return df

class TestGetStockData:

  def test_columns_present(self, stock_df):
    # ensures that the expected columns are all present
    assert "Open" in stock_df.columns
    assert "High" in stock_df.columns
    assert "Low" in stock_df.columns
    assert "Close" in stock_df.columns
    assert "Volume" in stock_df.columns

  def test_non_empty(self, stock_df):
    # ensures that there is more than one row of data
    assert len(stock_df.index) != 0

  def test_most_recent_within_week(self, stock_df):
    # most recent data was collected within the last week
    most_recent_date = pd.to_datetime(stock_df.index[0])
    assert (datetime.datetime.today() - most_recent_date).days <= 7

La création de tests unitaires peut sembler longue et fastidieuse, mais ils peuvent constituer un élément essentiel de tout pipeline CI et constituent des outils inestimables pour détecter les bogues dès le début avant qu'ils ne progressent dans le pipeline et ne deviennent plus coûteux à résoudre.

Si vous l'aimez, vous auriez dû le tester.

- Génie logiciel chez Google

Lectures complémentaires

Cette section fournit plus de ressources sur le sujet si vous souhaitez approfondir.

Bibliothèques

  • module unittest (et la liste des méthodes assert), https://docs.python.org/3/library/unittest.html
  • PyTest, https://docs.pytest.org/en/7.0.x/

Articles

  • Développement piloté par les tests (TDD), https://www.ibm.com/garage/method/practices/code/practice_test_driven_development/
  • Cadre de tests unitaires Python, http://pyunit.sourceforge.net/pyunit.html

Livres

  • Ingénierie logicielle chez Google, par Titus Winters, Tom Manshreck et Hyrum Wright https://www.amazon.com/dp/1492082791
  • Pratique de la programmation, par Brian Kernighan et Rob Pike (chapitres 5 et 6), https://www.amazon.com/dp/020161586X

Résumé

Dans cet article, vous avez découvert ce qu'est le test unitaire et comment utiliser deux bibliothèques populaires en Python pour effectuer des tests unitaires (PyUnit, PyTest). Vous avez également appris à configurer les tests unitaires et avez vu un exemple de cas d'utilisation des tests unitaires dans le pipeline de science des données.

Concrètement, vous avez appris :

  • qu'est-ce que le test unitaire et pourquoi il est utile
  • comment les tests unitaires s'intègrent dans le pipeline de développement piloté par les tests
  • comment faire des tests unitaires en Python en utilisant PyUnit et PyTest

Articles connexes