Licence CC BY-SA

Tests

Je vous ai plusieurs fois parlé des tests au long de ce cours, mais nous n’avons pas vraiment de bonne manière de les lancer. Voyons alors ce que propose Python pour ça.

Pytest

Pytest est une bibliothèque tierce fréquemment utilisée pour l’écriture de tests en Python, par la simplicité avec laquelle elle permet de décrire les cas de tests.

Premièrement vous pouvez installer Pytest avec la commande pip install pytest.
Celle-ci installe l’utilitaire pytest dans l’environnement courant.

Ensuite, il suffit d’utiliser la commande pytest, seule ou accompagnée de fichiers ou répertoires en arguments (par défaut il explorera le répertoire courant). Pytest se charge d’identifier les fichiers de tests, qui sont les fichiers Python préfixés de test_.
À l’intérieur de ces fichiers, les fonctions avec ce même préfixe sont identifiées comme des fonctions de tests.

Ainsi, les modules de tests que vous nous avons écrits précédemment, contenant des fonctions de tests formées d’assertions, sont déjà compatibles avec Pytest.

from operations import addition, soustraction


def test_addition():
    assert addition(3, 5) == 8
    assert addition(1, 0) == 1
    assert addition(5, -8) == -3


def test_soustraction():
    assert soustraction(8, 5) == 3
    assert soustraction(5, 8) == -3
    assert soustraction(1, 0) == 1
    assert soustraction(3, -5) == 8
test_operations.py
% pytest test_operations.py
======================== test session starts ========================
platform linux -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /home/antoine
collected 2 items


test_operations.py ..                                          [100%]

========================= 2 passed in 0.01s =========================

Tout se passe bien, nos fonctions valident les tests ! En cas d’erreur, Pytest s’arrête à la première assertion fausse de la fonction et affiche un rapport explicite du problème.

======================== test session starts ========================
platform linux -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /home/antoine
collected 2 items
  

test_operations.py F.                                          [100%]

============================== FAILURES =============================
___________________________ test_addition ___________________________

    def test_addition():
        assert addition(3, 5) == 8
        assert addition(1, 0) == 1
>       assert addition(5, -8) == 3
E       assert -3 == 3
E        +  where -3 = addition(5, -8)

test_operations.py:7: AssertionError
====================== short test summary info ======================
FAILED test_operations.py::test_addition - assert -3 == 3
==================== 1 failed, 1 passed in 0.02s ====================

Pytest permet d’aller plus loin que ça, et fournit des outils pour paramétrer facilement nos tests (générer différentes valeurs en entrée), abstraire les entrées et sorties standards (pour tester des fonctions qui utiliseraient print ou input) et bien d’autres encore que vous découvrirez dans sa documentation.

Unittest

Unittest est le module de la bibliothèque standard dédié à l’écriture de tests. Je ne vous en ai pas parlé jusqu’ici parce que celui-ci nécessite l’écriture de classes, qui ne sont abordées que dans le cours sur la programmation orientée objet en Python.

On peut en apprendre plus sur la page de documentation du module et on découvre notamment quelle structure respecter pour écrire une suite de tests.

import unittest

from operations import addition, soustraction


class TestOperations(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(addition(3, 5), 8)
        self.assertEqual(addition(1, 0), 1)
        self.assertEqual(addition(5, -8), -3)

    def test_soustraction(self):
        self.assertEqual(soustraction(8, 5), 3)
        self.assertEqual(soustraction(5, 8), -3)
        self.assertEqual(soustraction(1, 0), 1)
        self.assertEqual(soustraction(3, -5), 8)
test_operations.py

Il faut ainsi écrire une classe TestFooBar1 que l’on indique comme étant un cas de test (via unittest.TestCase entre parenthèses, qui signifie que notre classe dérive de TestCase) à l’interieur de laquelle on place nos fonctions de tests.

Ces fonctions possèdent un paramètre spécial self qui sera fourni automatiquement. Cet objet self possède différentes méthodes, notamment assertEqual pour vérifier que les deux arguments sont égaux, assertTrue qui revient à faire une assertion et assertFalse pour l’inverse (vérifier qu’une expression est fausse).

On peut exécuter un fichier de tests à l’aide de la commande python -m unittest.

% python -m unittest test_operations.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

En cas d’erreur(s), celles-ci sont aussi signalées par le programme.

% python -m unittest test_operations.py
F.
======================================================================
FAIL: test_addition (test_operations.TestOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/antoine/test_operations.py", line 10, in test_addition
    self.assertEqual(addition(5, -8), 3)
AssertionError: -3 != 3

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)

  1. Il est coutume d’utiliser un style CamelCase, où les différents mots qui forment le nom sont écrits avec une majuscule et ne sont pas séparés d'underscores.