Mesurer la qualité et la couverture du code
Contents
Cet article a pour objectif d’expliciter les notions de qualité et couverture de code et de montrer comment tout un chacun peut mettre en œuvre des métriques inclues dans gitlab sur son projet à Systerel.
Qu’est-ce que c’est ?¶
Qualité de code¶
La définition d’un code de qualité est contextuelle. Cependant, en termes généraux, un code de bonne qualité est :
Adapté à l’objectif (il fait ce qu’il est censé faire du point de vue du propriétaire)
Cohérent en interne (le style ne change pas d’un bout à l’autre)
Facile à comprendre (toute personne connaissant le langage de programmation utilisé doit être capable de lire le code et de comprendre rapidement son fonctionnement afin d’y apporter des améliorations).
Documenté de manière appropriée
Testable
Le code existe dans un but précis. Qu’il ait été écrit comme un exercice d’apprentissage ou qu’il soit un élément essentiel d’une application utilisée par des millions de personnes dans le monde, un code de bonne qualité aide les produits numériques et leurs fonctionnalités à faire leur travail de manière plus efficace et plus fiable.
En bref, une meilleure qualité de code signifie une meilleure qualité de logiciel.
Un code de bonne qualité peut également contribuer à protéger les utilisateurs, un logiciel bien écrit est plus sûr et plus sécurisé. C’est essentiel à une époque où les organismes de réglementation exigent de plus en plus des entreprises qu’elles prennent au sérieux la protection des données.
Couverture de code¶
En génie logiciel, la couverture de code est une mesure utilisée pour décrire le taux de code source exécuté d’un programme quand une suite de test est lancée. Ces informations permettent de vérifier quelle brique de code est parcouru, et donc testé.
Deux informations sont généralement retournés :
Le taux de couverture (en %)
Un rapport donnant la couverture effective par fichier
Comment ça marche ?¶
Le Gitlab Systerel intègre déjà ce genre de mesure. Pour les utiliser, il est nécessaire d’avoir des connaissances en intégration continue gitlab. Les informations adéquates peuvent se trouver sur le blog ici.
Les mesures qui nous intéressent correspondent à certains stage de de l’intégration continue.
Exemple¶
Projet interne — Composant SNMP Nomad Digital¶
Les mesures ont été mises en place sur le projet C943 à la demande du client Nomad Digital. La demande initiale était de donner à Nomad des KPIs de mesures de code utilisés par Systerel. A ce jour, il n’y a pas de KPI “générique” sur le sujet à Systerel. Une rapide étude a été faite pour utiliser ce qui était déjà inclus dans Gitlab et le mettre en exécution.
En version gratuite, les informations sont accessibles dans les merges request. Gitlab utilise :
CodeClimate pour la mesure de qualité de code

Affichage du rapport de qualité dans une MR du projet C943, composant SNMP
Des rapports au format Corbertura pour la couverture de code

Affichage du taux de couverture dans une MR du projet C943, composant SNMP

Affichage de la couverture par ligne dans le diff d’une MR du projet C943, composant SNMP
Mise en place¶
La documentation est disponible en ligne sur le site de gitlab:
https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html
https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html
Dans la suite nous allons vous montrer comment mettre en place ces mesures sur votre projet. Dans notre exemple on se base sur du code Python, mais le principe reste le même pour les autres langages supportés (JAVA…)
Instanciation du projet gitlab¶
Créer un projet gitlab est décrit ici : https://kvm-2.aix.systerel.fr/blog/internal/posts/2022-01/integration-continue-a-systerel/#creation-du-projet-gitlab
Dans notre cas, nous allons appeler notre repo “test_quality_code”.
Nous allons l’instancier avec les fichiers suivants :
main.py : script comptant le nombre de paramètre passé, et le comparant au chiffre 2.
import sys # total arguments n = len(sys.argv) print("Total arguments passed:", n) if n < 2: print ("Less than 2") elif n == 2: print ("Equal to 2") else: print ("Not less than 2")
.gitlab-ci.yml
image: python:latest run: script: - python main.py
Astuce : il est possible de créer / supprimer / éditer des fichiers directement sur Gitlab (pratique dans le cas de légère modification sans workflow défini) Astuce 2 : Gitlab reconnait automatiquement le fichier “.gitlab-ci.yml” et propose un “pipeline editor” : le fichier yaml est “compilé” en temps réel et permet de dire si sémantiquement le fichier est correct.
Après le push de ces 2 fichiers sur votre repo git, la pipeline doit se déclencher et être “success”. Comme aucun paramètre n’est passé, le résultat est “Less than 2”.
Update du .gitlab-ci.yml
pour ajouter de la couverture de code¶
La première étape et d’aller jusqu’à la création d’une merge request. Le workflow est à définir au niveau du projet, mais typiquement voici ce qui est fait: 1. Création d’une issue 2. A partir de l’issue, création d’une merge request avec création automatique de la branche
Maintenant que votre branche est créée, nous allons modifier le .gitlab-ci.yml
pour ajouter de la couverture de code :
image: python:latest run: #expression regulière qui vient parser la console de la pipeline pour récupérer la couverture globale et l'afficher dans la merge request. Va de paire avec "coverage report" coverage: '/TOTAL.*\s([.\d]+)%/' script: #installation de la librairie "coverage.py" utiliser pour la couverture de code - pip install coverage #modification de la ligne de lancement du python pour utiliser la librairie coverage.py - coverage run -m main #affiche le rapport dans la console, utilisé pour donner le pourcentage total de couverture - coverage report #génération d'un rapport xml - coverage xml #génération d'un rapport html - coverage html artifacts: #archivages des rapports XML (1 fichier) et HTML (dossiser htmlcov) paths: - coverage.xml - htmlcov/ #rapport au format cobertura ==> sera processé par Gitlab reports: cobertura: coverage.xml
Une fois la pipeline passée, le résultat est visible dans la merge request :
“Overview” : 62% de couverture
“Changes” : on passe uniquement dans la condition “n<2”

Résultat de la couverture de code dans l’example
vous pouvez également consulter les rapports XML et HTML dans les artefacts du build. Dans le cas des rapport HTML, le dossier complet est à télécharger pour afficher correctement le rapport (prise en compte du fichuer style.css présent).
TODO¶
Il est souvent nécessaire de réutiliser les résultats d’une tâche précédente. Par exemple pour dérouler les tests, on utilise l’exécutable produit pendant la phase de compilation. Gitlab utilise pour cela la notion d’artefacts.
Modifiez votre fichier .gitlab-cy.yml
comme suit pour ajouter un
artefact en phase de build
et l’utiliser dans les tests :
@@ -1,12 +1,17 @@ build-job: stage: build script: - - echo "Hello, $GITLAB_USER_LOGIN!" + - echo "Hello, $GITLAB_USER_LOGIN!" | tee "result.txt" + artifacts: + paths: + - result.txt + expire_in: 1 day test-job1: stage: test script: - echo "This job tests something" + - grep "Hello" result.txt test-job2: stage: test @@ -15,6 +20,7 @@ test-job2: - echo "After the echo commands complete, it runs the sleep command for 20 seconds" - echo "which simulates a test that runs 20 seconds longer than test-job1" - sleep 20 + - grep "Jonathan Livingston" result.txt deploy-prod: stage: deploy
La CI s’exécute et donne le résultat attendu suivant :

Affichage du pipeline modifié
On peut voir que le grep
de test-job1
a réussi, mais que celui de
test-job2
a échoué comme attendu et que la dernière tâche n’a pas
été exécutée, car elle dépend de la bonne exécution de l’étape (stage
) précédente.
Le fichier result.txt
est consultable sur l’interface du gitlab dans
la liste des artefacts associés à la tâche build-job
.
Bonnes pratiques¶
Avoir une CI rapide¶
L’idée d’une intégration continue est de permettre au développeur d’avoir un retour rapide sur ses modifications. Il est donc important :
de commiter et pousser souvent sa branche de développement,
de minimiser le temps d’exécution de la CI.
Dans le cas contraire, cela oblige le développeur à changer de contexte pour prendre en compte le résultat de la CI, ce qui est parfois compliqué.
On peut être amené à découper les tâches de la CI en différents groupes. Un premier qui est exécuté à chaque push et un second (par exemple pour des tests de performance qui peuvent être long à exécuter) qui seront exécutés toutes les nuits.
Travailler sur des branches¶
L’idéal est d’avoir en permanence un master pour lequel l’intégration continue est au vert. Chaque développeur qui démarre une branche pour développer une fonctionnalité sait alors qu’il part sur des bonnes bases et qu’il a la responsabilité de livrer une branche qui garde la CI au vert (par défaut, la CI est lancée sur toutes les branches qui sont poussées sur le gitlab).
Utiliser une image docker¶
Il est courant d’avoir besoin d’un environnement spécifique pour
exécuter les tests (un compilateur spécifique, un environnement de
développement python avec des paquets préinstallés, …). Pour cela on
utilisera une image docker dont l’adresse est spécifiée par le champ
image:
(global ou spécifique à un job). Par exemple :
doc: image: "docker.aix.systerel.fr/c462/sysdoc:0.1" script: - make
Les images peuvent provenir :
de dépôts publics comme Docker Hub,
du dépôt interne de Systerel : https://docker.aix.systerel.fr/,
d’une image fabriquée sur votre machine, voire par l’intégration continue et mise à disposition dans le registre d’image docker de votre projet : https://gitlab.aix.systerel.fr/USERNAME/test_ci/container_registry (nous avons des exemples de fabrication dans un job de l’intégration continue. N’hésitez pas à demander à la DT si vous avez ce genre de besoin).
Utiliser des services¶
La CI permet de réaliser des tests unitaires, mais aussi des tests d’intégration ou des tests fonctionnels. Par exemple, pour tester un serveur web, nous avons peut-être besoin d’avoir une base de données opérationnelle (postgres, mongodb, redis ou autre). Il est possible dans ce cas de définir un service associé au job au moyen d’une image docker. Concrètement, l’image sera démarrée avant l’exécution du job et le service inclus dans l’image sera disponible via le réseau.
Le job suivant définit le service redis utilisant l’image de même nom du
Docker Hub qui sera accessible par
le programme en test sous le nom redis
(il est aussi possible de
définir des
alias) :
test: stage: test services: - redis:6.2.5-alpine script: - cd go/src - go test -race -p 1 ./...
Mise au point de la CI¶
Il peut être difficile et un peu laborieux de mettre au point un fichier
.gitlab-ci.yml
: on modifie, on commit, on pousse, on voit qu’il y a
des erreurs et on recommence. Trois outils nous permettent d’être plus
efficace :
l’outil CILint disponible sur la page Pipelines votre projet. Vous pouvez y coller votre configuration et les erreurs de syntaxe ou de configuration seront signalées.
-
le lancement en local d’un job d’intégration continue via l’outil
gitlab-runner
:Installer
gitlab-runner
sur votre poste (voir les instructions),-
Lancer le job
tests
par la commandegitlab-runner exec docker tests
. Le paramètredocker
signifie que le job sera lancé dans une image docker (ce qui nécessite d’avoir un démon ou service docker activé). On peut utiliser le paramètreshell
à la place dedocker
si le job n’a pas besoin d’environnement spécifique pour s’exécuter.Cette utilisation reste limitée à des jobs simples (par exemple, la directive extends ne semble pas prise en compte et il n’est pas possible de dérouler tout un pipeline).
Utiliser l’outil tiers gitlab-ci-local qui devrait permettre de dérouler tout un pipeline en local. Je l’ai utilisé avec plus ou moins de succès en fonction de la complexité des jobs.
Note
Si vous avez des remarques ou questions à propos de cet article, n’hésitez pas à me contacter ou à ouvrir un ticket ou une merge request sur le projet gitlab du blog.
- 1
-
si vous avez besoin de mettre en place un miroir de dépôt de PCB vers le gitlab, faites une demande au SI.
- 2
-
un runner est un service qui est lancé sur une machine et qui communique avec l’instance gitlab pour savoir quels sont les jobs qui lui sont affectés.
À l’heure de l’écriture de cet article, un runner partagé est configuré sur la machine marouette (6 cpus i5-8500@3Ghz et 32 Go de RAM) dont vous pouvez visualiser l’occupation sur ce tableau de bord grafana.
Comments