.. _ref-test_flask: Les tests unitaires dans Flask ------------------------------ Les tests unitaires peuvent s'utiliser dans Flask tels que nous l'avons vu dans la section précédente. Nous allons reprendre l'exemple du site de Poudlard vu en cours. .. Il existe plusieurs frameworks de test en python. Les plus connus sont `unittest `_, et `pytest `_. Le wiki de Python contient une longue liste d'outils de test: https://wiki.python.org/moin/PythonTestingToolsTaxonomy Rappel du site de Poudlard ++++++++++++++++++++++++++ Le site de Poudlard a un modèle qui permet de récupérer les étudiants par maison. .. literalinclude:: ../python/poudlard/models/etudiant.py :language: python :linenos: Mais est-ce que cette fonction marche bien ? On va la tester. Ecriture d'un test ++++++++++++++++++ Nous allons écrire les tests dans un package « tests » (c'est-à-dire un dossier avec un fichier __init__.py vide) dans le dossier de notre application. Le fichier de test pour les étudiants peut s'appeler par exemple ``test_etudiant.py``. Nous créons une classe TestUser qui étend ``unittest.TestCase`` comme pour une application Python quelconque. Nous allons écrire un test pour la fonction ``get_etudiant_by_maison``. Nous allons pour l'instant assumer qu'il n'y a que l'étudiant Harry Potter de la maison Gryffondor dans la base de donnée pour l'instant. Nous reviendrons sur ce point plus tard. Nous allons tester si la fonction retourne bien un dictionnaire avec une seule maison, Gryffondor, et un seul étudiant dont le nom de famille est "Potter". .. code-block:: python class TestUser(unittest.TestCase): def test_search_by_maison(self): maisons_dict = get_etudiant_by_maison() self.assertEqual(len(maisons_dict), 1) maisons = list(maisons_dict.keys()) self.assertEqual(len(maisons), 1) self.assertEqual(maisons[0], 'Gryffondor') etudiants = maisons_dict['Gryffondor'] self.assertEqual(etudiants[0]['nom'], 'Potter') Base de donnée de test ++++++++++++++++++++++ Pour tester cette fonction, nous allons créer une base de donnée de test. En effet, il est important de tester les fonctions avec des données de test, et non pas avec les données réelles. Nous risquerions de corrompre la base de donnée de production. Il est de plus très difficile de réfléchir sur une grosse base de donnée pour écrire des tests. Unittest fournit une méthode ``setUp`` qui est appelée avant chaque test. Nous allons donc créer une base de donnée de test dans cette méthode. Nous allons aussi créer une méthode ``tearDown`` qui sera appelée après chaque test pour nettoyer la base de donnée. .. code-block:: python def setUp(self): # generate a temporary file for the test db self.db_fd, self.db_path = tempfile.mkstemp() # create the testapp with the temp file for the test db self.app = create_app({'TESTING': True, 'DATABASE': self.db_path}) self.app_context = self.app.app_context() self.app_context.push() self.db = get_db() # read in SQL for populating test data with open(os.path.join(os.path.dirname(__file__), "schema_test.sql"), "rb") as f: self.db.executescript(f.read().decode("utf8")) def tearDown(self): # closing the db and cleaning the temp file close_db() os.close(self.db_fd) os.unlink(self.db_path) ``schema_test.sql`` contient le code pour remplire la base de donnée de test. Soit vous la créez à la main, de toute pièce, soit vous utilisez la base de donnée réelle et vous la simplifiez. Pour la créer à partir de la base de données réelle : .. code-block:: console $ sqlite3 poudlard/poudlard.sqlite SQLite version 3.37.2 2022-01-06 13:25:41 Enter ".help" for usage hints. sqlite> .output schema_test.sql sqlite> .dump sqlite> .exit Et puis on simplifie, par exemple en ne gardant que Harry Potter dans les INSERT INTO: .. literalinclude:: ../python/poudlard/tests/schema_test.sql :language: sql :linenos: Lancer les tests ++++++++++++++++ Voici le fichier complet final ``test_etudiant.py`` : .. literalinclude:: ../python/poudlard/tests/test_etudiant.py :language: python :linenos: Pour lancer les tests, on peut utiliser la commande suivante : .. code-block:: console $ python3 -m unittest discover -v -s ./poudlard/tests test_search_by_maison (test_etudiant_model.TestUser) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.026s OK On peut aussi utiliser le module ``coverage`` pour voir le pourcentage de code couvert par les tests. Pour cela, on installe le module ``coverage`` avec pip, et on lance les tests avec la commande suivante : .. code-block:: console $ coverage run --source ./poudlard/ -m unittest discover -v -s ./poudlard/tests test_search_by_maison (test_etudiant_model.TestUser) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.027s OK Le résultat devrait ressembler à ceci : .. figure:: poudlard-coverage.png :align: center Intégration continue ++++++++++++++++++++ Ca serait très pratique si on pouvait lancer tous ces tests à chaque fois que quelqu’un push sur git, et envoyer un email si la branche est « cassée ». C’est ce que fait l’intégration continue. .. figure:: ci.jpg :align: center Dans le pipeline donné dans votre projet, l'étape "tests" lance les tests unitaires. Si un test échoue, le pipeline s'arrête et vous recevez un email. Si tous les tests passent, le pipeline continue. .. figure:: ci-coverage.jpg :align: center Si vous cliquez sur l'étape test, vous aurez également directement en ligne le rapport de coverage. Il est également repris sur la partie de droite.