I. Introduction▲
Cet outil, créé par Ned Batchelder en 2009, permet de mesurer la quantité de code couvert lors de l'exécution de tests. Le but est d'obtenir des informations afin de connaître les sections de code non testées et de créer ainsi les tests s'y rapportant.
Pour cela, l'outil est capable de générer différents types de rapports en sortie, lesquels aideront le développeur dans sa tâche de consolidation des tests dits de non-régression.
Ces derniers permettent de s'assurer, lors de la modification de code existant, qu'on ne modifie pas le comportement initial.
Coverage est compatible avec les branches 2 et 3 de Python.
Coverage est un outil qui ne permet que de surveiller quelles parties de code sont testées. Il ne sert nullement à vérifier le respect des bonnes règles de codage. Pour cela, je vous renvoie vers PEP8.py et Pylint.
II. Principe de fonctionnement▲
Le principe de fonctionnement est très simple. Nous appelons coverage, en lui passant la commande que nous lançons habituellement pour nos tests. Il va alors regarder les différents modules qui sont accédés, et va comparer la quantité de cas possibles avec les cas testés.
L'ensemble de ces données sera rangé dans un fichier de sortie qui pourra par la suite être exploité. Par exemple, il sera ainsi possible d'afficher pour chaque module le nombre de lignes et le nombre de cas testés.
Mieux ! Il sera possible de générer un rapport HTML. Un simple clic sur le nom du module ouvre alors le code complet, et une coloration nous permet alors d'identifier aisément les lignes non testées. Il est ainsi facile de créer des jeux de tests complets pour notre code.
III. Installation▲
Afin d'installer cet outil, il est conseillé d'utiliser pip ou easy_install :
pip install coverage
easy_install coverage
IV. Utilisation▲
IV-A. Cas de test▲
Afin d'étayer un peu les explications théoriques, nous allons nous appuyer sur un cas pratique. Pour cela, je vous propose le code suivant pour le fichier puissance.py :
# -*- Coding:utf-8 -*-
"""
Module de demo
"""
def
puissance2
(
nb):
"""
Calcule la puissance 2 d'un nombre
"""
if
nb ==
0
:
print
(
"Ce cas ne sera pas analyse"
)
return
nb **
2
def
puissance3
(
nb):
"""
Calcule la puissance 3 d'un nombre
"""
return
nb **
3
def
puissance4
(
nb):
"""
Calcule la puissance 4 d'un nombre
"""
return
nb **
4
Ensuite le fichier de test unitaire, TU.py, pour ce module :
# -*- Coding:utf-8 -*-
import
unittest
import
puissance
class
TestPuissance
(
unittest.TestCase):
def
test_puissance2
(
self):
retour =
puissance.puissance2
(
2
)
self.assertEqual
(
retour, 4
)
def
test_puissance3
(
self):
retour =
puissance.puissance3
(
2
)
self.assertEqual
(
retour, 8
)
def
test_puissance4
(
self):
retour =
puissance.puissance4
(
2
)
self.assertEqual
(
retour, 10
) # ce cas tombera en echec
if
__name__
==
"__main__"
:
unittest.main
(
)
Pour rappel, unittest est le module fourni avec Python et permettant de réaliser des tests unitaires. Pour plus d'informations, reportez-vous à cette pagehttp://python.developpez.com/tutoriels/python-en-bref/#LVIII-B-2-e.
Si nous lançons TU.py, via la commande « python TU.py », nous obtenons le résultat suivant :
Cela correspond bien à nos attentes. Cependant, aucune vérification sur le nombre suffisant de tests n'a été mise en place. Nous allons donc étudier à présent comment faire ceci.
IV-B. Lancement▲
L'utilisation de coverage est très simple. Il suffit d'utiliser la commande coverage run suivie de la ligne qu'on lancerait normalement.
|
À partir de maintenant, les commandes devront être lancées depuis le dossier contenant votre code. |
coverage run TU.py
Notons que la commande python est implicite ici. Avant vous lanciez python mon_script_de_test.py, vous lancerez désormais coverage run mon_script_de_test.py.
Une fois cette commande lancée, un fichier .coverage est créé. Celui-ci contient l'ensemble des éléments nécessaires pour l'exploitation future.
IV-B-1. Exclure certains modules de l'analyse▲
Analyser le code est une bonne chose, mais il peut être parfois nécessaire d'exclure certains modules. Dans notre exemple TU n'est pas à analyser, car il contient uniquement des tests unitaires. Voici la méthode pour faire cette exclusion.
coverage run --
omit TU.py TU.py
coverage report
Si on désire omettre plusieurs modules, il faut utiliser le caractère de séparation « , » et il ne doit surtout pas y avoir d'espace après les virgules.
Ex : --omit
Cette option fonctionne également avec la génération de rapport que nous allons voir après :
coverage run TU.py
coverage report --
omit TU.py
On constatera que seuls les résultats attendus sont affichés.
IV-B-2. Purger les anciennes données▲
Avant de lancer toute nouvelle mesure, il est recommandé de purger les mesures précédentes, afin d'éviter toute erreur :
coverage erase
IV-C. Rapport basique de test▲
Par défaut, coverage génère un rapport basique au format texte en ligne de commande :
|
L'option « -m » vous permettra d'avoir en plus les numéros de lignes. |
coverage report
On peut voir ici que 89 % du code de puissance.py est couvert. On a donc oublié un cas de test.
coverage report -
m
Avec l'option « -m », on peut identifier la/les ligne(s) posant problème, ici la ligne 11. Cette ligne correspond à notre if.
IV-C-1. Exclusion de modules lors de la génération du rapport▲
La procédure vue pour le lancement fonctionne aussi pour la génération du rapport de tests.
coverage run TU.py
coverage report --
omit TU.py
IV-C-2. Annoter directement le code source▲
Coverage permet aussi de lire directement le résultat dans le code :
coverage run TU.py
coverage annotate -
d ./
Annotate
En complément, l'option « -d » permet de demander une copie dans un dossier à spécifier. Ce sont ces copies qui seront annotées et non les originaux. Cela est très pratique pour faire un retour au développeur.
Trois caractères possibles sont alors disposés en tout début de ligne (position 0) :
Symbole |
Description |
> |
Ligne exécutée |
! |
Ligne non exécutée |
- ou rien |
Ligne ignorée (docstring, commentaires……) |
# -*- Coding:utf-8 -*-
> """
> Module de demo
> """
> def puissance2(nb):
> """
> Calcule la puissance 2 d'un nombre
> """
> if nb == 0:
! print("Ce cas ne sera pas analyse")
> return nb ** 2
> def puissance3(nb):
> """
> Calcule la puissance 3 d'un nombre
> """
> return nb ** 3
> def puissance4(nb):
> """
> Calcule la puissance 4 d'un nombre
> """
> return nb ** 4
IV-D. Rapport XML▲
Il peut aussi être intéressant de générer le rapport au format XML. Coverage permet de faire ceci simplement avec la commande ci-dessous :
coverage xml
<?xml version="1.0" ?>
<!DOCTYPE coverage
SYSTEM
'http://cobertura.sourceforge.net/xml/coverage-03.dtd'
>
<coverage
branch-rate
=
"0"
line-rate
=
"0.8889"
timestamp
=
"1411860249373"
version
=
"3.7.1"
>
<!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
<packages>
<package
branch-rate
=
"0"
complexity
=
"0"
line-rate
=
"0.8889"
name
=
""
>
<classes>
<class
branch-rate
=
"0"
complexity
=
"0"
filename
=
"puissance.py"
line-rate
=
"0.8889"
name
=
"puissance"
>
<methods/>
<lines>
<line
hits
=
"1"
number
=
"2"
/>
<line
hits
=
"1"
number
=
"6"
/>
<line
hits
=
"1"
number
=
"10"
/>
<line
hits
=
"0"
number
=
"11"
/>
<line
hits
=
"1"
number
=
"12"
/>
<line
hits
=
"1"
number
=
"14"
/>
<line
hits
=
"1"
number
=
"18"
/>
<line
hits
=
"1"
number
=
"20"
/>
<line
hits
=
"1"
number
=
"24"
/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
Ce rapport XML, associé au fichier source, vous permettra d'appréhender aisément la structure type des rapports.
IV-E. Rapport HTML basique avec coverage▲
La création d'un rapport au format HTML est une des fonctionnalités les plus intéressantes de coverage.
La page d'index comprendra la liste des modules analysés, et en cliquant sur un nom, le code apparaîtra avec une coloration vous permettant d'identifier aisément les cas non traités.
coverage html
coverage html -
d ./
dossier_sortie
L'option « -d » permet de spécifier un dossier de sortie. Par défaut le dossier sera « ./htmlcov ».
Nous voyons sur cette image qu'une ligne est surlignée en rouge. Il s'agit du cas non traité de notre module.
V. Interprétation des résultats▲
Quel que soit le type de rapport choisi, vous disposerez systématiquement de toutes les informations utiles.
De plus, si vous choisissez les rapports HTML, vous aurez la possibilité d'afficher pour chaque module, le code avec un surlignage vous permettant d'identifier rapidement les manques.
Voici la signification des différentes colonnes fournies :
Désignation |
Description |
Statements, Stmts |
Nombre de cas à tester |
Missing, Miss |
Nombre de cas non traités |
Excluded |
Nombre de cas non pris en compte (commentaires, docstrings…) |
Coverage, Cover |
Taux de couverture du code |
VI. Allons plus loin▲
Les tests que nous avons effectués jusqu'ici, ayant pour but de découvrir coverage, constituent ce qu'on appelle des tests par instructions.
Il faut entendre par là que le pourcentage de couverture est calculé en prenant en compte le nombre de lignes exécutées par rapport au nombre total de lignes.
Dans notre exemple précédent, sur les neuf lignes présentes, une seule n'est pas exécutée. Cela signifie donc que 89 % des lignes de notre code est exécuté.
Cependant, il se peut que l'on désire que l'unité de nos analyses ne soit pas la ligne mais la branche. Nous analysons alors non plus notre code ligne par ligne, mais par ensemble de lignes. Nous allons ainsi tester chaque combinaison que le code peut générer. Par exemple un if peut générer deux combinaisons, selon que son prédicat soit vrai ou non.
Pour demander à coverage de changer de mode, il suffit d'utiliser l'option « --branch ».
Cette option permet de mesurer non pas quelle proportion de lignes de code a été testée, mais quelle proportion des combinaisons possibles du code a été exécutée.
Comme le montrent ces captures d'écran, le taux de couverture est passé de 89 % à 82 %.
Si nos résultats sont flagrants sur du code possédant peu de lignes, il en va différemment sur du code de plusieurs milliers de lignes. En analysant votre code avec ces deux modes que nous venons de voir, vous pourrez aisément mettre en évidence les portions non testées.
VII. Conclusion▲
La qualité d'un code est une chose importante, car c'est elle qui peut assurer, entre autres, la fiabilité de celui-ci.
Comme nous venons de le voir, coverage.py est un outil très pratique permettant l'amélioration du code Python. Cet outil permet aux développeurs d'améliorer leur travail.
Toutefois attention, car si coverage est extrêmement pratique, il dispose de limites.
En effet, l'analyse des tests de code par ligne et par branche ne sont pas les seuls tests de couverture existants. Il existe une multitude de types de couverture (fonctions, instructions, conditions multiples…) plus ou moins adaptés à des domaines précis (aéronautique, militaire, médical…).
Aussi, si vous désirez utiliser coverage pour vos projets, assurez-vous bien de l'adéquation de l'outil avec vos contraintes.
VIII. Remerciements▲
Je tiens à remercier les personnes suivantes pour leur aide durant la rédaction de cet article :