Couverture de code pour Python avec Nose et Coverage
Toujours dans la série de billet consacrée aux tests unitaires avec Python, nous allons voir cette fois ci une façon de visualiser, par console ou html, la quantité de code que couvre les tests. Un billet un peu plus cours que les deux précédant, mais également plus rapide à prendre en main.
Lorsque le code et le nombre de tests correspondant grossi, il est utile de trouver un moyen d’en faire un bilan, il existe pour cela la couverture de code, sûrement plus connu sous son nom anglais de code coverage. C’est ce que l’on va regarder dans ce billet, à l’aide de Nose et de Coverage.
Installation
Si vous n’avez pas déjà installé Nose, faite le comme suit, ou consulter le billet Améliorer les tests unitaires de Python avec Nose pour plus de détails sur nose.
Pour installer coverage, la procédure est similaire, on peut déjà noté que coverage n’est utilisé uniquement ici pour générer l’html.
Les utilisateurs d’Arch Linux pourront le retrouvé avec l’AUR
A tool for measuring code coverage of Python programs.
On note que le paquet est marqué périmé, cependant la version 3.5 n’est sortie que depuis un mois, et cette version doit certainement faire l’affaire.
Usage console
Reprenons l’exemple précédant, une fonction add, auquel on rajoute une fonction multiply, mais sans lui rajouter de tests. La première fonction est testé de diverses façons (doctest, unittest, framework test de nose), alors que la seconde n’en comporte aucun.
Le fichier src/chiffres.py
ressemble maintenant à ça :
def add (a, b):
if isinstance(a, basestring) or isinstance(b, basestring):
raise ValueError
return a+b
def multiply(a, b):
if isinstance(a, basestring) or isinstance(b, basestring):
raise ValueError
return a*b
On utilise l’argument --with-coverage
de Nose, ou tout simplement rajouter with-coverage=1
dans le ~/.noserc
.
On effectue les tests précédemment écrit, attention à basestring qui n’existe que pour Python2.x mais plus dans Python3 :
test_add_string (test_chiffres.TestChiffres) ... ok
[.....]
test_spec_chiffres.test_should_raise_an_exception_with_two_string ... ok
Name Stmts Miss Cover Missing
----------------------------------------
chiffres 8 3 63% 9-11
----------------------------------------------------------------------
Ran 10 tests in 0.046s
OK
L’important est de remarquer le pourcentage, mais également le 9-11
correspondant au lignes pour lequel il ne comporte pas de tests. L’exemple
étant petit, on pouvait retrouver le résultat de tête facilement.
Une alternative est d’utiliser directement coverage comme suit :
Name Stmts Miss Cover
----------------------------------------------
src/chiffres 8 3 63%
tests/test_chiffres 13 1 92%
tests/test_nose_chiffres 8 0 100%
tests/test_spec_chiffres 8 0 100%
----------------------------------------------
TOTAL 37 4 89%
Il manque cependant l’indication sur les lignes manquantes, sauf si je l’ai raté, mais il n’est peut être pas utile de toujours l’avoir.
Sortie HTML
Nose vient avec un argument --cover-html
, cependant il est inutile de perdre
du temps à essayer de le faire fonctionner, il comporte un bug depuis quelques
années, le développeur le sait, et il est probable que cela reste comme ça
encore longtemps, l’auteur encourage l’utilisation de coverage
qui produit de
toute façon un meilleur html (selon ses dires).
Utiliser nosetests --with-coverage
avant coverage lui sert de hook.
[...]
$ coverage html
Le résultat se trouvera dans le répertoire htmlcov/
pour lequel il suffit par
exemple de faire :
On obtiendra un résultat similaire à l’image suivante :
Un exemple plus réel et navigable est visitable sur ce lien.Voilà pour une présentation rapide d’une utilisation un peu spécifique de Nose, mais pouvant être utile. Et nous voilà avec la fin de ce billet à mi-chemin de cette série consacré aux tests avec Python.