Python - test unitaire

Préambule : toute fonction Python n’a pas vocation à accueillir des tests unitaires.
Les tests unitaires ne sont utiles que pour des fonctions "outils", pas pour des codes complexes avec mes milliers de paramètres.

Débuter les tests unitaires

Principe

Le test unitaire consiste à tester le comportement d’une fonction pour s’assurer que pour une entrée donnée, le résultat donne bien la sortie attendue.
Cela permet notamment de s’assurer de la non-regression (qu’une modification dans le code ne va pas créer une erreur qui se propage dans un code existant).

Installation

Ici, j’utilise le paquet converage.py (https://coverage.readthedocs.io/en/coverage-5.0.4/index.html)

$ pip3 install coverage

Utilisation

Intro Ici, le test va s’effectuer automatiquement, sans que l’on ait à préciser le nom des fichiers : l’avantage est que cela permet d’avoir plusieurs fichiers tests/test_monmodule1.py assez petit et donc qu’il est facile de s’y retrouver.
L’inconvénient est que l’on n’a pas la main sur tout...

Remarque : Il faut se placer dans le dossier ./tests (si on y a mis ses tests)

* Avant de lancer le test, il faut s’assurer qu’il n’y ait pas d’anciennes données, sans quoi le test risque de se faire dedans aussi, ce qui ne marchera pas !

$ coverage erase

* Lancer le test, avec une détection automatique des fichiers sources

$ python3 --branch -m unittest discover

* Créer le rapport

$ coverage report

* Créer une copie des sources dans ./tests/Annotate et y mettre les annotations d’utilisation

$ coverage annotate -d ./Annotate

Test et gitlab

Intégration continue


Extrait du .gitlab-ci.yml

test:
  stage: test
  script:
    - echo "Testing..."
    - pip3 install .[test]
    - cd tests && coverage erase
    - coverage run -m unittest discover
    - coverage report
    - coverage xml
    - sed -i 's|" filename="|" filename="./|g' coverage.xml
  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
  artifacts:
    paths:
      - tests/coverage.xml
    reports:
      coverage_report:
        coverage_format: cobertura
        path: tests/coverage.xml

Quelques remarques :
* Ici, les modules pour le test sont précisé dans extra_requires/doc du fichier setup.cfg
* Je ne pourrais pas dire à quoi servent individuellement toutes ces lignes (en particulier le sed, et les balises "coverage" et "artifacts", mais visiblement, elles sont nécessaires. Quand je saurais, j’éditerais cet article.

Ajout d’un badge dans Gitlab

Parceque c’est inutile, donc indispensable, mais tout de même plus joli ^^
(sans compter que ça donne mauvaise conscience d’avoir un mauvais rating...)

Il se présente sous la forme :

![coverage](https://gitlab.example.com/<namespace>/<project>/badges/<branch>/coverage.svg?job=coverage)

Et on peut avoir le lien directement en allant dans :
`project > settings > CI/CD > General Pipelines [expand] > Pipeline Status

Ressources

* Visualisation dans Gitlab : https://docs.gitlab.com/ee/ci/testing/test_coverage_visualization.html
* Inclure à la doc Sphinx : https://stackoverflow.com/questions/65944916/how-to-use-sphixs-sphinx-ext-coverages-coverage-show-missing-items https://www.sphinx-doc.org/ar/master/usage/extensions/coverage.html
* https://gricad-gitlab.univ-grenoble-alpes.fr/continuous-everything/ci
* https://docs.gitlab.com/ee/ci/testing/test_coverage_visualization.html#python-example