Approfondissement Technique : Intégration continue

Au cours de ma cinquième année à l’Exia.CESI, j’ai réalisé un projet d’approfondissement technique au sein d’un groupe de 4 personnes. Chaque membre du projet devait choisir un sujet qu’il connaissait et souhaitait approfondir. Chacun a donc choisi un sujet différent en fonction de ses affinités et de son choix d’orientation professionnelle. Nous nous sommes alors réunis pour définir un projet qui nous permettrait de mettre en pratique nos sujets. Le projet Skilldr est ainsi né. Je souhaite maintenant vous présenter le contenu de mon approfondissement sur l’intégration continue en Java.

ProcessIntegrationContinue

Cet article est extrait de notre rapport de projet. Il décrit l’intégration continue, ses objectifs et les outils que nous avons mis en place dans notre projet. Enfin, la dernière partie présente la manière dont nous avons mis en place les processus d’intégration continue tout au long du développement de notre application.

L’intégration continue est un processus appliqué aux projets de développement logiciel pour s’assurer de la continuité du fonctionnement du projet à tout moment de son développement. Il est de plus en plus utilisé en entreprise pour permettre l’amélioration de la qualité du code et la collaboration entre les différents acteurs du projet.

Pourquoi ce sujet ?

Lors de mon précédent stage, j’ai appris l’existence d’un ensemble d’outils Java réservés à l’intégration continue. Ces outils n’étaient pas beaucoup utilisés sur notre application car celle-ci était assez ancienne et le temps disponible était insuffisant pour les mettre en place. Cependant, j’ai décidé de les appliquer dans le cadre d’un projet personnel. Ils m’ont permis d’améliorer mon code et d’éviter des erreurs qui dans le cadre d’un projet en groupe auraient été bloquantes pour les autres développeurs.

Mon choix de sujet d’approfondissement technique s’est donc porté sur ce domaine avec la volonté d’approfondir ma connaissance de celui-ci. Je souhaite également élargir mes compétences à l’ensemble des processus techniques mis en place autour du développement d’applications Java afin d’améliorer et d’optimiser mes propres développements. Enfin, l’intégration est une problématique que de plus en plus d’entreprises souhaitent simplifier. L’intégration continue est un moyen assez simple d’améliorer la productivité de tous les développeurs en assurant la continuité dans le fonctionnement de l’application.

Intégration continue

L’intégration est le processus de partage de code entre développeurs sur un dépôt commun. A chaque intégration, les codes sont réunis et échanger entre eux.

L’intégration continue est une pratique de vérification du code à chacune de ses intégrations ayant pour but d’éviter la régression du code ou l’apparition d’une anomalie au sein de celui-ci. Le code est vérifié par un système automatisé de construction qui va valider son bon fonctionnement et sa bonne intégration au reste de l’application existante sur le dépôt. Cela permet de s’assurer que le code peut être compilé sans erreur après chaque partage. Les tests réalisés par les développeurs sont également exécutés après la construction pour valider le fonctionnement de l’application. Ce processus peut être mis place tout au long d’un projet, il est cependant conseillé de le mettre en place dès le début du projet.

L’intégration de code individuel peut être une tâche extrêmement compliquée dépendant principalement de la communication entre les développeurs et le travail effectué avant le projet. Chaque développeur doit récupérer le code des autres par un moyen non adapté (clé USB) et vérifie qu’il s’intègre bien à son propre travail. L’intégration continue permet de s’assurer qu’à tout moment, nous sommes capables d’avoir une version fonctionnelle de l’application. Cela évite d’avoir des erreurs dû à un travail trop indépendant de tous les développeurs. En effet, des travaux menés trop longtemps en parallèle sans intégration mènent souvent à une longue période durant laquelle il faut rendre ces ensembles fonctionnels entre eux. Ce n’est pas un outil mais une pratique issue de l’eXtreme Programming.

Pour résumer les principaux avantages de l’intégration continue sont les suivants :

  1. Compilation du code et valide son bon fonctionnement
  2. Réalisation de tests unitaires et de tests d’intégration
  3. Mise à disposition d’une version testable de l’application
  4. Rédaction de rapport sur la qualité du code, la couverture des tests…

Il me paraît important de souligner le second point. Un programme, notamment en développement Java EE, peut-être compilé sans erreur et cependant lever une erreur dès son déploiement. Dans la procédure de construction de l’intégration continue, la compilation et l’exécution des tests sont deux étapes très importantes pour la vérification du fonctionnement de l’application.

Les tests

Il existe deux types de tests en fonction du cadre de leur exécution et de leurs objectifs : les tests unitaires et les tests d’intégration.

Les tests unitaires

Les tests unitaires sont des procédures destinées à vérifier le bon fonctionnement d’une petite partie de code source, ou unité de code. Les tests unitaires doivent être rédigés en parallèle du code métier d’une application. Pendant longtemps, les tests unitaires ont été considérés comme étant secondaires par rapport au code principal d’une application. Aujourd’hui, ils ont une importance majeure, une méthode les met d’ailleurs au premier plan : TDD (Test Driven Développement). Dans cette méthode, les tests sont rédigés avant les fonctionnalités. Les tests correspondent aux spécifications de la fonction testée, celle-ci doit répondre exactement ce qui est attendu par le test.

Les tests unitaires permettent de détecter très rapidement si une erreur a été commise lors du développement. Qu’ils soient rédigés avant la fonctionnalité testée ou en même temps, leur exécution doit détecter les différences entre le fonctionnement actuel et ce qui est attendu de la part de ce code. Ils sont conservés tout au long du développement du projet et exécutés de manière régulière afin de détecter les possibles régressions. Dans un projet où plusieurs développeurs travaillent sur le même code source, il est possible qu’un développeur modifie le code d’un second pour sa propre utilisation sans savoir où ce code était utilisé auparavant. Si cette modification a un impact sur les fonctionnalités précédemment développées, les tests unitaires correspondant ne seront plus validés. Les tests permettent donc d’assurer la stabilité du code source tout au long de sa vie, du développement jusqu’à la maintenance.

Lors de l’exécution des tests unitaires, il est possible d’avoir des statistiques sur les temps d’exécution d’une fonctionnalité particulière. Ceci permet de réaliser une optimisation des blocs de code qui sont les plus longs à exécuter dans le programme.

Pour être pertinents, les tests unitaires doivent couvrir un maximum des fonctionnalités de l’application. Les rapports de couverture de code aide les responsables à identifier quelle partie du code n’est pas assez testée en fonction de la sensibilité du code en question. Réaliser trop de tests n’est pas un bon comportement non plus. Lors de la maintenance d’un projet, il peut être nécessaire de modifier certaines anciennes classes entrainant des erreurs dans les tests. Il faut donc modifier les tests pour qu’ils correspondent aux nouvelles exigences de l’interface. Si des tests non pertinents ont été écrits cela va allonger le temps nécessaire à cette phase de maintenance.

Les tests d’intégration

Les tests d’intégration sont exécutés à la suite des tests unitaires. Ces tests ont pour objectif de tester le fonctionnement de l’application dans des conditions similaires aux conditions de production. L’objectif n’est plus de tester le fonctionnement unitaire de chaque bloc de code mais plutôt un cas d’utilisation de l’application, une fonctionnalité utilisant plusieurs blocs unitaires. Ainsi ces tests accèdent à ces interfaces comme à des boîtes noires et doivent déterminés si tous les cas d’utilisation de cette fonctionnalité répondent bien aux spécifications définies. Tout comme les tests unitaires, ils doivent être réalisés en parallèle des fonctionnalités et toutes les tester. Mais un nombre trop important de tests peut ralentir certaines phases du projet.

Les tests d’intégration sont plus complexes à mettre en place en fonction de la technologie utilisée. Certaines technologies ont besoin d’être exécutées au sein de conteneurs complexes comme les EJBs du Java EE. Il peut être plus complexe donc de réaliser des tests automatisés car une phase de déploiement supplémentaire est nécessaire. Les tests d’intégration complexes sont donc généralement exécutés manuellement par des utilisateurs avant la mise en production.

Les outils de l’intégration continue

Dans cette partie, je vais présenter tous les outils que nous avons utilisés pendant notre projet afin d’assurer l’intégration continue. Certains de ces outils n’ont qu’un lien limité avec le sujet mais ils ont leur importance dans le fonctionnement général des processus d’intégration de l’application et doivent donc être mentionnés ici.

Apache Maven

L’Apache Software Foundation est un acteur très important du monde Java. Elle a développé de nombreux outils et librairies pour le développement d’applications Java et Java EE. Apache Maven est l’un d’entre eux. Cet outil est réservé à la gestion et à l’automatisation de la production des projets en Java. Il permet la production d’un logiciel à partir de ses sources en optimisant les tâches intermédiaires et en garantissant le bon ordre de fabrication. Il permet de supprimer les tâches difficiles du processus de build. Les versions 1 et 2 de Maven ont été créées en parallèle. Les versions suivantes du logiciel utiliseront le modèle de Maven 2. Ce logiciel est publié sous licence libre Apache 2.0. Je ne parlerai ici que de Maven 2 et 3 qui apparaitront sans distinction sous le nom générique de Maven.

mavenMaven se base sur une approche déclarative grâce à un fichier XML situé à la base du projet, le Project Object Model ou POM. Chaque projet est configuré dans ce fichier à travers plusieurs balises. L’architecture de ce document XML permet aux projets Maven d’être divisés en sous-modules. Ces sous modules possèdent chacun un pom.xml et héritent de la configuration établie dans le pom parent situé à la racine du projet. Ce fonctionnement permet le bon découpage de la configuration mais aussi sa factorisation.

Maven permet la déclaration de dépendances qui seront résolues lors de la compilation du projet. Ainsi, lorsqu’une librairie est nécessaire pour le fonctionnement du projet, elle doit être déclarée en dépendance dans le pom.xml. Maven va la télécharger et la stocker dans un dépôt local. Il pourra ainsi l’insérer dans le package final de l’application dans le dossier lib. Un dépôt central de Maven est disponible en ligne et paramétré automatiquement à chaque installation. Ce dépôt contient de nombreuses librairies avec leurs versions. Ce dépôt est ouvert ce qui peut entrainer des erreurs ou des incohérences sur certains projets qui ne sont pas beaucoup utilisés. Ce fonctionnement simplifie grandement le développement sur plusieurs postes de développement. En effet, les développeurs n’ont pas besoin de télécharger eux-mêmes les librairies qui sont trop imposantes pour passer par l’outil de gestion de versions. Des profils sont également paramétrables via ce document XML. Ils apportent une configuration dynamique en fonction du profil choisi lors de la construction (par exemple : la définition de propriétés contenant les accès à la base de données pour avoir une base par environnement de travail).

Les créateurs de Maven ont vraiment souhaité simplifier son fonctionnement en utilisant des conventions plutôt que des configurations complexes. Ainsi, l’architecture des projets Maven respecte la convention suivante :

  • / : racine du projet contenant le pom.xml
  • /src : les sources du projet.
  • /src/main : les fichiers l’application principale du projet
  • /src/main/java : les sources java
  • /src/main/resources : les ressources du projet (configuration, propriétés, …)
  • /src/main/webapp : les fichiers liés aux applications Web (HTML, JavaScript, CSS, et configuration web.xml)
  • /src/test : les fichiers de tests de l’application
  • /src/test/java : les fichiers Java de tests de l’application
  • /src/test/resources : les ressources des tests (jeux de données, configuration, …)
  • /target : tous les fichiers résultats, les résultats des tests, les packages générés (War, Jar

Cette convention peut être modifiée par l’ajout de configuration.

Maven possède un cycle de vie assez strict mais dont chaque étape passe obligatoirement après la validation de la précédente

  • compile
  • test
  • package
  • install
  • deploy

Ainsi, l’exécution d’une étape va tout d’abord vérifier si l’étape précédente a bien été exécutée et elle sera lancée si c’est le cas. Ces étapes du cycle de vie de Maven sont appelées des goals. D’autres goals sont disponibles mais ils ne font pas partie du cycle de vie normal de construction de Maven (clean, assembly, site…). Ils permettent principalement d’ajouter des actions à réaliser en plus de la construction du projet, comme par exemple la suppression des anciennes constructions.

Apache Maven est l’outil le plus utilisé aujourd’hui dans la gestion de construction de projets Java. Son principal concurrent est Apache Ant mais celui-ci est beaucoup plus complexe à configurer. Contrairement à Maven qui se base sur des conventions pour simplifier la configuration, Ant va nécessiter une configuration poussée pour réaliser un projet simple.

Git

La gestion de version ou Versionning est un système très important dans le cadre de développement de projets logiciels. Chaque développeur va partager de manière régulière son code sur un dépôt commun pour le projet. Chaque partage, appelé commit, va contenir les nouvelles versions des fichiers de code. Ces versions sont conservées tout au long de la vie du projet pour permettre un suivi de l’évolution du code. En cas d’erreur provoquée à une certaine version, il est donc possible de revenir à une version antérieure à l’erreur. Pour réaliser cette gestion de versions, il existe de nombreux logiciels : Git, Subversion, Mercurial, CVS… Tous ces logiciels ont un fonctionnement commun qui est le partage des codes sources sur un dépôt. Mais deux branches majeures se détachent par la suite. Les systèmes de gestion de versions centralisés sont orientés autour d’un unique dépôt de versions de référence. Seul le dépôt central délivre des numéros de versions pour les partages effectués. Ce type de gestion est plus léger car il limite le nombre de versions mais est plus contraignant car il empêche le travail hors connexion ou le travail sur des branches expérimentales. Subversion est un système de gestion de versions centralisé. Le second type de gestion est la décentralisée qui permet le travail individuel et désynchronisé et par la suite le partage entre collaborateurs. Il existe plusieurs dépôts pour un même logiciel, le principal et celui de chacun des développeurs. Chacun de ces dépôts peut générer des numéros de versions qui seront récupérés par les autres en cas de partage du code. Ce type de gestion possède de nombreux avantages :

  • pas de point sensible unique, le dépôt central n’est pas le seul à posséder toutes les versions
  • permet le travail hors connexion
  • le merge-request est une demande faite au dépôt central pour ajouter son code à la branche principale, l’administrateur donne les droits sur le dépôt après avoir vu le travail réalisé
  • le travail brouillon ou expérimental est réalisé en local
  • le dépôt central ne possède que des versions de partage en théorie fonctionnelles

Son principal inconvénient est le fait que le nombre de versions soit beaucoup plus important. Si chaque utilisateur réalise plusieurs versionning de son coté, lors de la mise en commun, l’historique des versions grandit d’autant. La récupération du dépôt est donc plus longue et lourde à cause du téléchargement de toutes ces versions.

gitlogoclairGit est un des systèmes de gestion décentralisée les plus important aujourd’hui. Il est notament très utilisé dans le cadre des projets open sources. Le projet a été initié par Linus Torvald, créateur de la première version du noyau Linux, et est publié sous licence libre GPL2. Il est disponible sur toutes les plateformes et possède plusieurs interfaces graphiques entièrement conçues par la communauté de développeurs du projet ou par des entreprises. GitLab est une interface Web développée par l’entreprise GitLab B.V. sous licence MIT. Cette interface permet la gestion des dépôts de code Git sur une machine GNU/Linux. Il rend facilement accessible la gestion des utilisateurs et de leurs droits d’accès aux dépôts ainsi que le parcours du code et la gestion de demandes de fusion. En juillet 2013, le projet a été scindé par l’entreprise en deux projets distincts GitLab Community Edition sous licence libre et GitLab Enterprise Edition sous licence propriétaire.

Nous avons choisi de travailler avec Git car c’est un système que nous avons l’habitude d’utiliser et que nous souhaitons maitriser. Cet outil est de plus en plus utilisé et ses fonctionnalités notamment au niveau du travail hors connexion nous paraissaient très intéressantes. Pour l’interface graphique client, nous utilisons l’interface graphique fournit avec le logiciel. Cela nous permet d’être plus précis dans les fichiers qui composeront la version et évite l’ajout de fichiers de tests modifiés par chaque développeur. Pour le choix de l’interface Web, nous avons cherché l’outil possédant la documentation la plus claire. Le premier essai avec Gitorious n’ayant pas été concluant, nous avons choisi d’installer GitLab qui répond à tous nos besoins en matière de gestion des dépôts Git.

Jenkins

Jenkins est un projet open sources écrit en Java. Il est originaire du projet Hudson initié par SunMicrosystems. Au rachat de Sun par Oracle, ce dernier a souhaité imposer une licence restrictive sur le projet Hudson. La communauté a alors décidé de réaliser un fork, c’est à dire récupérer le code libre de Hudson pour continuer le projet sous le nom de Jenkins.

Serveur d'intégration continue JenkinsCet outil permet la réalisation de l’intégration continue de projets de développement logiciels. Il s’exécute sur un serveur grâce un conteneur de servlet comme Apache Tomcat. A la réception d’un message, il va exécuter une procédure de construction du logiciel. Cette procédure de construction va suivre différentes étapes importantes dont la réalisation des tests (avec Junit par exemple qui est intégré nativement). Différents plugins permettent le paramétrage de ces procédures de construction. Il est possible d’utiliser Maven pour réaliser la construction ce qui permet de bénéficier de tous ses avantages concernant la configuration et la gestion des dépendances. Le message initiateur de la construction peut prendre plusieurs formes : un commit sur le système de gestion de versions du projet, l’activation d’une tâche périodique (CRON), la fin de la construction d’un autre projet ou à l’appel d’une URL spécifique. Ainsi, il est possible de réaliser une construction à chaque partage de code afin de vérifier que le code envoyé ne cause pas d’erreur sur l’application entière. Cela permet de s’assurer du bon fonctionnement de l’application à tout moment de son cycle de vie. Mais la réalisation d’une construction et de tous les tests associés est une activité consommatrice de ressources serveurs. Il faut donc paramétrer la construction en fonction des besoins et des ressources que l’on peut allouer à Jenkins.

Une fois le message reçu, Jenkins va lancer la procédure paramétrée de construction. Si une erreur est rencontrée lors de la construction, par exemple une erreur de compilation des sources du projet, la construction est marquée en échec. Les personnes désignées reçoivent un mail indiquant l’échec avec un lien vers le rapport de la construction. Dans ce rapport est présent le log complet de la construction qui permet aux personnes responsables de l’intégration d’identifier l’erreur. En cas de succès, les tests sont exécutés. En cas d’échec de l’un des tests, la construction est indiquée instable. En cas de succès de toute la procédure, tous les rapports sont publiés.

Parmi les concurrents de Jenkins, on retrouve CruiseControl publié sous licence BSD. Cet outil permet la construction de code source Java et .NET via CruiseControl .NET. Il possède une interface graphique illustrant l’évolution de la construction mais la configuration doit être réalisée manuellement dans les fichiers XML du logiciel. Apache Continuum est également un concurrent de Jenkins qui est souvent revenu lors de nos recherches. Lui aussi open sources, Apache Continuum est principalement dirigé vers le Java. Tout comme CruiseControl la configuration se fait directement dans les fichiers XML. La majeure partie des fonctionnalités reste très similaire entre ces outils. On remarque que tous fonctionnent avec des plugins qui permettent de personnaliser l’outil et de lui ajouter des fonctionnalités notamment avec les outils de gestion de versions, l’envoi de mails, ou la construction par Maven.

Jacoco

Jacoco (Java Code Coverage) est un outil destiné à analyser les tests durant leur excéution afin de fournir des rapports de couverture de code. Il est installable sur l’environnement de développement Eclipse et permet la récupération des rapports directement au sein de l’outil. Il est distribué sous licence Eclipse Public Licence.

JacocoPour la réalisation de son étude de couverture, Jacoco va ajouter des instructions au bytecode lors de son exécution. Pour cela, il est lancé en tant que Java Agent, c’est à dire qu’il est exécuté dans la JVM en parallèle des tests avec un accès privilégié à la zone mémoire réservée aux tests. Cette instrumentation du code permet la récupération de données d’utilisation du code source et donc avoir un rapport très détaillé.

Les rapports de Jacoco sont générés en HTML, ils sont donc visualisables très facilement. Jenkins permet d’avoir une synthèse des rapports de couverture ainsi qu’un accès facilité aux fichiers de HTML de Jacoco.

Dans notre recherche d’un outil de couverture de code, nous avons aussi rencontré Cobertura. Cobertura propose des fonctionalités semblables à Jacoco en termes de rapport de couverture de code. Son fonctionnement est différent car pour faire une étude de la couverture, il faut exécuter une commande Maven particulière qui va modifier le code généré lors de la construction. Ce code va permettre la récupération des données de couverture. Malheureusement, cette commande Maven relance une construction complète de l’application ce qui consomme beaucoup trop de ressources serveur. La configuration de Jacoco est donc plus légère que Cobertura pour des qualités de rapports équivalentes.

Qualité logicielle et SonarQube

La qualité logicielle est une appréciation générale du logiciel basée sur plusieurs indicateurs. La manière dont les fonctionnalités sont implémentées en respectant l’architecture spécifiée lors de la définition des spécificités fonctionnelles est un indicateur orienté vers le fonctionnel du logiciel. La qualité structurelle est la manière dont les besoins non-fonctionnels permettent l’atteinte des objectifs fonctionnels comme par exemple la maintenabilité du code ou sa résistance. Tout l’aspect fonctionnel d’un logiciel ne peut être évalué que par des utilisateurs informés des spécifications attendues et par les tests réalisés tout au long de la vie de l’application. Le structurel en revanche peut être évalué au niveau du code source du logiciel par des outils externes. Le Consortium for IT Software Quality (consortium souhaitant créer un standard de la qualité logicielle) a défini cinq principales caractéristiques nécessaires au code source d’un logiciel pour apporter une plus-value à leur créateur :

  • Fiabilité : mesure le risque que l’application s’arrête de manière non contrôlée
  • Efficacité : assure les performances de l’application
  • Sécurité
  • Maintenabilité : contient les notions d’adaptabilité, de portabilité et de transférabilité à une autre équipe de développement
  • Taille adaptée aux besoins

De nombreuses théories ont été énoncées sur le sujet de la qualité logicielle. Je n’irai pas plus loin sur ce sujet puisque ce n’est pas le cœur de cet approfondissement technique.

Outil de mesure de la qualité SonarqubeDans le cadre de ce projet, nous avons mis en place un outil d’analyse de la qualité du code : SonarQube, publié sous licence LGPL. Cet outil réalise des analyses du code source à la demande. Il utilise un Sonar Runner qui va parcourir tous les fichiers sources afin d’analyser leur contenu et identifier les défauts. Les relevés concernent principalement les indicateurs de qualité structuraux du code source. Une interface Web permet de voir les rapports présentés dans différents widgets. Si les analyses sont réalisées régulièrement, il est possible d’avoir un historique de l’évolution de tous les indicateurs. Cette interface permet également la visualisation des défauts directement dans le code source et l’attribution des défauts à certains développeurs pour correction.

De nombreux plugins sont disponibles afin de compléter les analyses de Sonar. Par exemple, le plugin Jacoco récupère les rapports fait par Jacoco lors de la construction pour afficher les résultats au sein de l’interface de Sonar. Ces plugins offrent également un large éventail de langages couverts par les analyses, plus de 25 aujourd’hui. SonarQube est soutenu par une communauté importante ce qui lui permet d’être intégré à de nombreux outils comme Jenkins ou même l’environnement de développement Eclipse.

L’analyse de SonarQube est composée de deux étapes : la récupération de données statiques et la comparaison à des règles prédéfinies. Dans les données statiques, on retrouve le taux de commentaires par rapport aux lignes d’instruction, le taux de méthodes publiques commentées, le taux de code dupliqué ou la complexité des classes et méthodes. Ces données sont purement informatives et doivent être analysées par un responsable pour en tirer les conclusions nécessaires. Par exemple, un code dont toutes les méthodes et classes publiques sont commentées peut être de qualité même si cela représente peu de lignes de commentaires. De même, la duplication peut apparaitre sur des méthodes mais ne pas être gênante, certaines d’entre elles étant très proches dans leur implémentation comme par exemple la méthode equals(). La comparaison avec des règles prédéfinies fait référence aux profils de qualité. Dans SonarQue, il est possible de choisir un profil contenant un ensemble de règles de code associées à une criticité. Par exemple, la convention de nommage de Java pour les variables est une règle de criticité Majeure dans le profil par défaut. Cette règle, si elle est activée lors de l’analyse, va alors lever un défaut pour toutes les variables ne respectant pas cette convention. Les défauts seront visibles dans l’interface avec un texte explicatif concernant cette règle. Les règles sont entièrement paramétrables, il est donc possible de les désactiver si elles ne sont pas pertinentes pour le projet. Il est également possible de définir ses propres règles afin de personnaliser l’analyse.

Cet outil d’analyse de la qualité ne peut pas vérifier toute la partie fonctionnelle de l’application mais en revanche elle peut permettre d’uniformiser les développements et donc assurer une meilleure maintenabilité. La performance est également améliorée autour de certains tests. Par exemple, un test sur une valeur NULL va être considéré comme un défaut si une NullPointerException avait été levée plus tôt dans le code.

Les concurrents de SonarQube sont assez nombreux. Nous en avons étudié quelques-uns avant de faire notre choix. JTest, Structure101, Coverity et JArchitect sont des outils de qualité propriétaires développés par Parasoft, Headway, Coverity Inc et CoderGears. Leur fonctionnement est assez proche de celui de Sonar et ils fournissent des analyses complètes sur tous types de défauts. Pour tous ces produits le prix des licences est assez élevé, de 400$ pour une licence personnelle à 4000$ pour un poste de développement. Jdepend est lui sous licence BSD, il permet une analyse complète du code mais son interface est moins facile à prendre en main que celle de SonarQube et la navigation et la recherche des défauts sont plus complexes. Le choix de SonarQube était économique et fonctionnel. Son intégration aux autres outils et ses fonctionnalités nous permet de pousser plus loin la qualité de nos développements.

Arquillian

Dans le cadre de notre projet, nous utilisons des Enterprise Java Beans (EJB) et des EntityManager. Ce sont des objets qui sont injectés par le serveur d’applications lorsque l’application a besoin de l’un d’entre eux. Ce fonctionnement est optimisé pour que l’application ne soit pas ni ralentie par des conflits sur un nombre d’instances limitées, ni ralentie par un nombre trop important d’instances. Leur utilisation est donc intéressante mais elle complique notre travail au niveau des tests. En effet, les tests unitaires ne sont pas réalisés au sein d’un serveur d’applications. Il nous fallait donc réaliser de nombreux tests d’intégrations pour couvrir notre application. Afin de simplifier la réalisation de ces tests d’intégration, nous avons utilisé Arquillian.

Framework de Test ArquillianC’est un Framework de test développé par JBoss qui permet la réalisation des tests à l’intérieur d’un conteneur embarqué ou distant. Arquillian s’intègre parfaitement aux Frameworks de tests comme JUnit ou TestNG ainsi qu’aux environnements de développement ou plugins de tests Maven. Grâce à cette intégration, il est facile de créer des tests d’intégration car ils sont très semblables aux tests unitaires. Lors de l’écriture du test JUnit, il suffit de paramétrer le lanceur d’Arquillian. Ce lanceur va tout d’abord exécuter une méthode annotée qui va lister les classes nécessaires à l’exécution de ce test et créer une archive. Il va déployer l’archive sur le conteneur paramétré et lancer le test d’intégration. Une fois le test terminé, l’archive est supprimée du conteneur. Dans le cas d’un serveur embarqué, Arquillian sera chargé de son démarrage et de son arrêt.

L’objectif principal des développeurs d’Arquillian est vraiment de fournir un outil simple d’utilisation pour réaliser des tests d’intégration. Son architecture permet une grande souplesse de paramétrage. Son fonctionnement permet d’avoir de véritables rapports de tests automatisés sur des classes situées dans un environnement fonctionnel. Il permet d’accéder à nos EJBs directement depuis nos classes de tests sans configuration supplémentaire que n’en ont les classes métiers.

Lors de nos développements, nous n’avons pas trouvé d’alternatives automatisées à Arquillian. Plusieurs outils existent afin de réaliser des déploiements automatisés d’une application sur un conteneur (Cargo) puis lancer les tests JUnit mais leur fonctionnement est moins intégré au processus complet des tests JUnit. Arquillian fournit l’avantage d’automatiser les tâches d’injection de dépendances et les connections aux conteneurs, ce qui simplifie le travail des développeurs qui peuvent se concentrer sur les tests fonctionnels.

Installation sur le serveur

Dans le cadre de nos études, notre équipe de projet loue un serveur chez l’hébergeur OVH. Ce serveur fonctionne avec une version Debian 7 de GNU/Linux.

Pour la réalisation de notre projet, j’ai donc mis en place tous les outils Maven, Git et son interface GitLab CE, Jenkins, Jacoco et SonarQube sur notre serveur. Le paramétrage de tous ces outils est assez facile. Etant tous open sources et possédant une communauté assez active, la documentation pour procéder à leur installation est assez claire.

Serveur d'application JBossPour notre application Java EE, nous avons choisi JBoss AS en tant que serveur d’applications. Notre souhait était d’apprendre à maitriser cet outil qui est aujourd’hui le premier serveur d’applications certifié Java EE utilisé en entreprise. JBoss est développé par RedHat qui a séparé la version Application Server de la version Enterprise Application Platform. A partir de la version 8 de JBoss AS, il a été décidé de changer son nom pour WildFly afin d’éviter les erreurs. Nous utilisons la version JBoss AS 7.1 sortie en Février 2012 et entièrement certifiée Java EE.

Pour notre base de données, nous utilisons MySQL qui est open sources sous licence GPL Cette base de données est très simple à installer, la configuration de sécurité est plus complexe mais très rapidement mise en place pour un accès uniquement local.

Processus d’intégration continue

Le processus d’intégration continue se déroule tout au long de la phase de développement sur les postes de développement et sur le serveur. Nous avons mis en place trois environnements afin de réaliser nos développements. Le premier est constitué des postes de développement de tous les développeurs. Chaque utilisateurs a installé les outils nécessaires à son travail sur son poste (Eclipse, NetBeans, Git, Jboss, Maven…). Cet environnement contient donc une base de données avec uniquement des données de tests. Sur le serveur, nous avons deux environnements fonctionnant en parallèle. L’environnement d’intégration est celui sur lequel sont exécutés tous les tests serveurs. Une base de données est allouée spécifiquement pour l’intégration. Le dernier environnement est celui de production sur lequel sera déployée la version cliente de l’application. Cette version permettra l’inscription des utilisateurs. Dans notre processus de développement, les versions de l’application passent des environnements de développement à l’intégration sur lequel elles sont testées en profondeur. Une fois les tests réalisés, une nouvelle version peut être publiée en production. Ce fonctionnement nous permet de nous assurer du bon fonctionnement de l’application dans un environnement semblable à celui de production avant de le mettre à disposition des utilisateurs. Les bases de données sont séparées afin d’éviter le mélange des données de tests et les données réelles des utilisateurs. Chaque environnement possède un profil Maven qui nous permet de paramétrer les éléments tels que la base de données et les fichiers de logs de sortie.

Poste de développement

Dans le cadre de ce projet nous avons mis en place de nombreuses règles de développement afin d’assurer une continuité des différents fichiers de code.

Notre volonté première était de réaliser une application open sources. Pour cela nous avons décidé que l’ensemble du code doit être rédigé en anglais afin de le rendre compréhensible par le maximum de personnes. Puis, nous avons mis en place des conventions de nommage qui sont celles des applications Java en général. Chaque poste possède une référence aux dépôts Git commun avec la possibilité de créer plusieurs branches en fonction du travail accompli. Les branches sont des concepts de gestion de versions qui permettent d’avoir des chemins parallèles de l’historique des versions. Ces chemins parallèles évoluent donc en même temps mais ne partagent que ce qui précède la création de la branche. La fusion de branches permet de réunir des branches en fusionnant les codes sources de celles-ci. Ce système permet de travailler en parallèle sur la branche principale et sur des tâches plus lourdes, plus volumineuses sans qu’elles ne se gênent les unes les autres.

L’architecture générale du projet est définie en avant du projet avec le découpage en sous-module pour avoir un réel découpage des ensembles basé sur les fonctions qu’ils contiennent. Ce sous découpage est facilité par l’utilisation de Maven qui permet très facilement de faire des liens entre les différents sous-modules.

Chaque développeur Java du projet possède une instance de Maven sur son poste. Lors de la phase de développement un profil particulier est utilisé pour les développeurs. Ce profil leur permet de réaliser leurs tests de développement sur un serveur d’application et une base de données locale. Ce profil permet le paramétrage des accès à la base de données ainsi que différentes propriétés pour les tests et les logs. En cas de besoin d’une librairie externe, un ajout de dépendance aux pom.xml du projet leur permet de très facilement l’ajouter. Cette modification est presque transparente pour les autres développeurs. Lorsqu’ils récupèrent la nouvelle version du pom.xml à partir du dépôt Git, leur Maven sera mis à jour et la dépendance téléchargée.

Serveur

L’intégration continue au niveau du serveur commence au moment où un développeur partage du code sur le dépôt Git. A ce moment, Git va réaliser ces traitements de versionning et de sauvegarde d’historique. Il va ensuite appeler une URL précisée dans la configuration du projet avec un certain nombre d’informations sur le projet. Cette adresse est liée à l’instance Jenkins du serveur.

A la réception de cette requête, Jenkins va se servir du contenu de la requête pour identifier le projet concerné. Ensuite, l’outil va lancer une construction pour le projet. Cette construction est exécutée avec Maven. Le profil d’intégration est choisi afin que les paramètres utilisés ne soit plus ceux du profil développeurs défini par défaut. Maven va donc démarrer la construction de tous les modules les uns après les autres dans l’ordre nécessaire à la résolution des dépendances.

Après la construction de chacun des modules, Maven va exécuter tous les tests du module. Dans le profil d’intégration, on retrouve la configuration pour Jacoco. Ce dernier est lancé en tant que Java Agent pour pouvoir récupérer les données concernant la couverture des tests. Si une erreur a lieu dans cette phase de tests, la construction continue mais elle sera indiquée comme instable. Dans l’interface graphique de Jenkins les erreurs au sein des tests sont très visibles et on retrouve des comptes rendus individuels de chacun d’entre eux ainsi que leurs logs très utiles en cas d’erreur. Les tests unitaires et d’intégration sont exécutés en même temps avec JUnit et Arquillian. Les tests menés par Arquillian sont exécutés sur le serveur d’applications JBoss AS installé sur le serveur. Il est possible de séparer les résultats des tests unitaires et des tests d’intégration mais dans notre cas, cette division n’est pas pertinente.

Une fois les tests exécutés, Jenkins va faire une demande d’analyse à SonarQube. L’analyse est réalisée sur tous les fichiers du projet. Sonar va alors faire un rapport avec cette analyse afin de relever tous les défauts qu’il a rencontré. Ces défauts seront visibles dans l’interface Web de Sonar et devront être corrigés. A la réception du mail de Sonar, le responsable de l’intégration continue attribue les défauts aux personnes qui les ont créés ou à des personnes plus à même de les corriger. Enfin, certains cas sont déclarés Faux Positifs car dans la situation dans laquelle ils sont relevés par SonarQube, ils ne relèvent pas de défauts de qualité. Jenkins va récupérer une partie des logs émis par SonarQube lors de son analyse et va les insérer dans sa propre sortie.

La prochaine étape de Jenkins est le déploiement de l’archive de l’application. Dans ce projet nous travaillons sur de multiples environnements côté serveur. Ceci nous permet de déployer une version d’intégration du projet afin de réaliser nos tests. Jenkins va se charger de réaliser ce déploiement. Pour cela, il va faire appel à un script Bash qui va copier le War généré par la construction dans un dossier spécifique à JBoss. Le script va également créer un fichier vide. Ce fichier va être détecté par JBoss qui va automatiquement déployer l’archive associée. L’application est alors accessible depuis un navigateur Web. Ainsi les nouvelles versions de l’application sont toujours en ligne, disponibles pour les développeurs afin de réaliser des tests d’intégration manuels.

Conclusion

Tout au long de notre projet, l’intégration continue que nous avons mise en place nous a permis de gagner du temps et d’améliorer notre code. A travers tous les outils que nous avons mis en place durant ce projet, nous pouvons nous assurer du bon fonctionnement de l’application à tout moment. Le développement est lui-même simplifié par l’utilisation d’un outil qui gère lui-même les dépendances du projet. Git et la gestion des versions permet aux développeurs de travailler sur le même code sans avoir de difficultés à les partager. Jenkins nous assure la viabilité de l’application tout au long de son développement. Les tests unitaires et d’intégration réalisés avec JUnit et Arquillian assure la non régréssion et le bon fonctionnement de l’application et de l’environnement serveur. La qualité de notre code et l’uniformité de celui-ci sont garanti par les analyses SonarQube, ce qui rend le projet plus viable à long terme grâce aux indicateurs de sécurité, de performances et de maintenabilité. Le déploiement automatique de l’application sur le serveur d’applications JBoss permet à tous les membres de l’équipe de voir l’évolution du projet, de faire des tests directement sur l’environnement d’intégration et s’assurer du bon fonctionnement de celui-ci. L’intégration continue est un élément majeur de la phase de développement d’une application. Son emploi systématique dans des projets d’envergure peut réduire les coûts de maintenance qui peuvent être très importants dans le cas de projets clients volumineux. Pour les développeurs, l’intégration continue fournit une évaluation permanente du travail ce qui leur permet de l’améliorer en continue et de ne pas ralentir les autres membres du projet.

Cet article est entièrement issu de notre rapport de projet qui est publié sous CC-BY. Vous y trouverez également un descriptif de l’application ainsi que les parties de mes camarades sur les méthodes Agiles (Clément DAVID), les EJBs (Pierrick KNECHT) et les Single Page Applications (Pierre LALLEMENT). Le code de l’application (qui n’est pas terminée) est disponible sous licence CC-BY sur notre dépôt Git.

spacer

Laisser un commentaire