Coverage pour définir le taux de couverture des tests d'un programme Python

Publié le 25 janvier 2010 par Luc
Bonjour à tous,

La préparation d'une formation Python avancée m'a permis de me plonger un peu plus sur un outil très intéressant pour le taux de couverture de test d'un programme Python. Cet outil s'appelle coverage et a été écrit par Ned Batchelder.

Calcul du taux de couverture


Le taux de couverture d'un test est une mesure qui a pour but d'indiquer quelle quantité du code a été exercée par un jeu de tests. Cette mesure peut servir parfois à définir une clause d'arrêt pour une campagne de test. Mais attention au piège. Une couverture de 100% ne signifie en aucun cas qu'il ne reste aucun bug.

Il existe plusieurs méthodes pour calculer le taux de couverture:
  • par instructions: chaque ligne de code d'un programme a été exécutée au moins une fois
  • par branches: on prend en compte les branchements liés à l'évaluation d'une condition (un if par exemple)
  • par chemins: on vérifie que tous les cas possibles ont été exécutés. Ce calcul est plus difficile et n'est pour l'instant pas géré par coverage


Imaginons le programme suivant

def foo(x):
    print "Un"
    if x > 2:
        print "Deux"
    if x > 3:
        toto = "Trois"
    print toto
foo(4)


Couverture par instructions


Coverage est situé dans le répertoire Script de l'installation Python. Une mesure se fait en deux étapes: L'exécution du code instrumenté et la génération d'un rapport au format HTML ou texte.

Depuis une ligne de commande, exécutez:

"C:\...\Python26\Scripts\coverage.exe" run myapp.py

Cette commande va charger, instrumenter et exécuter le script. Notez que l'option -L permet d'inclure ou non le code d'une librairie Python installée.

Une fois l'exécution terminée vous pouvez générer un rapport au format HTML.

"C:\...\Python26\Scripts\coverage.exe" report -d C:/coverage

coverage génere les pages HTML dans le répertoire et propose un fichier index.html, point d'entré du rapport.



Notre code d'exemple comporte 8 instructions et l'appel à la fonction foo avec la valeur 4 comme paramètre a permis de les exécuter toutes. Le taux de couverture est de 100%! Parfait! Le code est-il pour autant exempt de bugs. Bien entendu, non puisque si nous avions appelé foo(3), une erreur serait intervenue: "UnboundLocalError: local variable 'toto' referenced before assignment". Notre jeu de test est donc incomplet.

Couverture par branches


On voit donc ici les limites d'un taux de couverture par instructions. Une méthode de calcul un peu meilleure consiste à utiliser un calcul par branches.

Notre exemple comporte en plus de ces 8 instructions, 4 branches possibles:


L'option --branch de coverage permet de lui faire prendre en considération les branches du programmes dans son calcul de taux de couverture:

"C:\...\Python26\Scripts\coverage.exe" run --branch myapp.py



On voit à présent que le taux de couverture n'est plus que de 83%. 2 branches n'ont pas été exécutés par le test et on a donc une valeur de couverture égale à 8 instructions + 2 branches exécutés divisés par un total de 12.

On voit que la valeur du taux de couverture n'est pas absolue. On pourrait avoir un très grand nombre d'instructions qui ferait remonter cette valeur proche de 100% sans pour autant que la qualité du code ne soit améliorée.

Pourtant cette valeur est un indicateur intéressant car elle va permettre d'appréhender l'avancement d'une campagne de tests. Le rapport généré par Coverage permettra par exemple de voir précisément quelles sont les lignes de code qui ont été exécutées et celles qui ne le sont pas. Coverage donne une visibilité sur les tests qu'il reste à écrire.

Coverage est donc un outil très intéressant pour le développement avec Python. Comme tout outil, il est nécessaire de l'utiliser à bon escient et de savoir analyser les résultats retournés.