.. highlight:: console .. _tests: Tests ##### Les tests sont une étape importante dans l'écriture d'un code robuste et fiable. Ils doivent permettre de s'assurer que les différentes parties d'un programme -- fonctions, classes, méthodes, etc. -- retournent les valeurs justes, se comportent conformément aux attentes et interagissent correctement. En outre, ils doivent permettre d'identifier rapidement les évolutions négatives du code (*régression*): ainsi, lorsqu'une nouvelle fonctionnalité ne passe pas les tests qui y sont associés, il doit être évident que l'erreur provient de ce développement récent et non des fonctions ou objets que cette partie de code utilise. On distingue hiérarchiquement trois types de tests: 1. Les *tests unitaires* vérifient individuellement chacune des fonctions, méthodes, etc.; 2. Les *tests d'intégration* évaluent les interactions entre différentes unités du programmes; 3. Les *tests système* assurent le bon fonctionnement du programme dans sa globalité. Tests unitaires =============== .. index:: pair: tests; unitaires pair: module; doctest pair: module; pytest Nous nous concentrerons dans ce cours sur les *tests unitaires*, qui doivent contrôler le comportement *individuel* des différentes parties, non seulement *a.* le résultat retourné selon les paramètres d'entrée (validité et précision d'un calcul, cas limites, etc.), mais également *b.* le comportement vis-à-vis des paramètres non conformes (p.ex. de type incorrect) ou *c.* des situations anormales (p.ex. l'absence d'un fichier). Il existe plusieurs façons de rédiger les tests unitaires d'une fonction, méthode ou classe. * Un *doctest* est un exemple (assez simple) d'exécution de code *directement inclus dans la docstring* de la fonction ou méthode: .. literalinclude:: mean_power.py :language: python :pyobject: mean_power :emphasize-lines: 12-15 Les *doctests* se limitent en général à des tests simples et pédagogiques, qui illustrent la documentation. Ils peuvent être exécutés de différentes façons: - avec le module standard :mod:`doctest`:: $ python -m doctest -v code.py - avec le module externe pytest_ [#py.test]_:: $ pytest --doctest-modules -v code.py * Les fonctions de test dédiées permettent d'effectuer des tests plus poussés que les *doctests*, généralement dans un fichier séparé du code à tester (p.ex. dans un répertoire dédié `tests/`). P.ex.: .. literalinclude:: ../Exercices/animaux.py :language: python :start-after: start-tests :end-before: end-tests L'écriture de ces fonctions dépend de la librairie de test utilisée. Nous utiliserons la librairie :pypi:`pytest`, qui s'avère relativement simple à mettre en oeuvre (toutes les fonctions dont le nom commence par `test_` et contenant des `assert` sont automatiquement détectées par pytest_) tout en étant assez riche. Dans ce cas, les tests sont exécutés via la commande:: $ pytest programme.py ou juste:: $ pytest dans la racine du projet (voir pyyc_ pour un exemple pratique). Tout comportement décrit dans la documentation doit faire l'objet d'un test, et inversement. En outre, dans l'idéal, chaque ligne de code, *a priori* nécessaire, doit être testée pour s'assurer de sa pertinence et de son utilité (taux de couverture maximal). L'écriture de tests unitaires impose de fait de bien respecter le *principe de responsabilité unique* (voir :ref:`/cours/code.txt#principes-de-conception-logicielle`): chaque élément de code (classe, méthode, fonction) ne doit avoir qu'une unique raison d'exister, avec une interface bien définie et une « utilisabilité » circonscrite à même d'être facilement testées. Il est très utile de transformer toutes les fonctions ou procédures de vérification écrites au cours du développement et du débogage en tests, ce qui permet de les réutiliser lorsque l'on veut compléter ou améliorer une partie du code. Si le nouveau code passe toujours les anciens tests, on est alors sûr de ne pas avoir cassé les fonctionnalités précédentes (régressions). Pour un exemple d'utilisation des tests unitaires et d'évaluation du taux de couverture, voir pyyc_. .. _TDD: Développement piloté par les tests ================================== Le *Test Driven Development* (TDD) est une méthode de programmation qui permet d'éviter des bogues *a priori* plutôt que de les résoudre *a posteriori*. Ce n'est pas une méthode propre à Python, elle est largement utilisée par les programmeurs professionnels. Le cycle préconisé par TDD comporte cinq étapes: 1. écrire un premier test; 2. vérifier qu'il échoue (puisque le code qu'il teste n'existe pas encore), afin de s'assurer que le test est valide et exécuté; 3. écrire un code minimal pour passer le test; 4. vérifier que le test passe alors correctement; 5. éventuellement « réusiner » le code (*refactoring*), c'est-à-dire l'améliorer (rapidité, lisibilité) tout en gardant les mêmes fonctionnalités. .. rubric:: Notes de bas de page .. [#py.test] pytest_ ne fait pas partie de la bibliothèque standard, il vous faudra donc l'installer indépendemment. Le module :mod:`unittest` de la bibliothèque standard permet à peu près la même chose que pytest_, mais avec une syntaxe souvent plus lourde. .. _pytest: https://docs.pytest.org/ .. _pyyc: https://ycopin.pages.in2p3.fr/pyyc/ .. |fr| image:: ../_static/france_flag_icon.png :alt: Fr .. |en| image:: ../_static/uk_flag_icon.png :alt: En