Note : Ce contenu a été créé avant que Fabernovel ne fasse partie du groupe EY, le 5 juillet 2022.
Au sein de FABERNOVEL, nous développons des applications, et donc si l’on reprend notre métaphore, nous construisons des voitures tous les jours. Nous devons nous assurer que chaque modification effectuée n’altère pas la qualité de nos produits.
Or tester en permanence nos applications dans les moindres recoins afin de vérifier leur stabilité et le respect des spécifications fonctionnelles représente une charge très lourde. Nous avons donc mis en place un ensemble de règles et un système de vérification pour assurer la cohérence, la stabilité et le bon fonctionnement de nos systèmes.
I . Cohérence, homogénéité et donc évolutivité
I.I Intro
Dans notre exemple, le projet de construction de voiture prend forme : les plans et le design ont été définis, comme le sont l’architecture et les spécifications fonctionnelles pour l’application mobile.
Une application mobile est dite maintenable si chacun peut lui apporter des modifications sans introduire de bugs. La première étape pour le garantir est de s’assurer que le code est cohérent et homogène. En effet, si celui-ci respecte les bonnes pratiques d’usage, il sera plus compréhensible par l’ensemble des équipes impliquées et donc plus facilement modifiable.
Le code doit donc être :
- Compréhensible : la modification d’un code qui n’est pas clair sur son fonctionnement va probablement engendrer des effets indésirables au sein de notre application (ie des régressions). Il s’agit par exemple de bien ranger le code ou encore de nommer explicitement les fonctions et les variables.
- Homogène : Si ce n’est jamais le même type de vis qui est utilisé pour toutes les roues de la voiture, chaque démontage et remontage sera long et fastidieux. Il en est de même pour le développement d’une application mobile. Il faut s’assurer que des normes comme le coding style, ou encore l’architecture du code soient respectées au sein d’une équipe de développement. Ces normes n’ont pas pour but de formater le code produit par un développeur, mais plutôt de lui apporter un cadre. Elles permettent d’améliorer la compréhension du code et de faciliter l’intégration de nouveaux développeurs ou de fonctionnalités au sein d’un projet.
I.2 L’analyse statique
Il faut s’assurer à chaque étape de la construction de la voiture que l’assemblage respecte les règles que vous vous êtes fixées.
Aujourd’hui, l’assemblage des voitures se fait en grande partie par des robots. Les bonnes pratiques définies ne peuvent donc pas être transgressées. Ce n’est pas le cas des applications mobiles, encore développées par des humains dont les décisions influencent le fonctionnement. Si un nouveau développeur intègre l’équipe, il n’aura pas forcément en tête les règles établies et risque donc de ne pas respecter l’homogénéité du projet. On pourrait éventuellement vérifier manuellement que son travail est en harmonie avec celui effectué sur le projet, mais ceci serait très coûteux.
Au sein de FABERNOVEL, nous utilisons des outils d’analyse statique de code qui nous permettent, à partir de règles objectives que nous avons définies, de valider que chaque modification effectuée sur le projet est conforme.
Ces règles sont très nombreuses et couvrent divers domaines tels que :
- Du formatage pur de code (saut de ligne, indentation, code mort…)
- Des choix de patterns de code plutôt que d’autres (utilisation de variables ou objets immutable...)
- La complexité du code (longueur des fonctions, complexité cyclomatique...)
- L’utilisation de certains frameworks
- ...
Nous parlons d’analyse statique car celle-ci ne vérifie pas le fonctionnel de notre application, à l’instar d’un robot qui vérifie le type de vis utilisées dans le montage de la voiture sans vérifier que les pièces vissées tiennent ensemble.
I.3 La code-review
L’analyse statique vérifie de manière infaillible que le code testé respecte les règles de style définies par l’équipe. Cependant, certains problèmes ne peuvent être détectés à l’aide de cette analyse, comme par exemple les problèmes d’architecture de code. Il est donc important d’introduire un processus de revue de code humaine lors du développement de l’application. Ce processus, baptisé code-review, permet notamment :
- De vérifier que l’architecture du code est correcte
- De vérifier que le code est lisible et compréhensible par un autre humain que celui l’ayant écrit (élements bien nommés, bien rangés)
- De faire une première passe de revue fonctionnelle (= le code a l’air logique au regard des spécifications)
Pour en savoir plus sur la code review chez FABERNOVEL, nous vous invitons à (re)lire cet article.
I. 4 Le tableau de bord : Sonarqube
Dans une voiture, il est important d’avoir un tableau de bord pour avoir un retour sur les actions utilisateurs et leurs conséquences, mais aussi pour afficher l’état des systèmes de la voiture. Par exemple, il est difficile de se rendre compte quand une variation du niveau de la pression des pneus se produit s’il n’y a pas d’alerte au niveau du tableau de bord.
De la même manière, les meilleurs outils d’analyse, s’ils n’ont pas un affichage clair et précis, ne permettent pas de juger la santé de son projet de développement d’application mobile. C’est pour cela que nous utilisons Sonarqube chez FABERNOVEL. Cet outil nous permet de visualiser des métriques sur nos projets comme la dette technique, la couverture de code ou encore la complexité du projet. La dette technique correspond au temps nécessaire pour corriger tous les problèmes issus du non respect des règles définies ; la couverture de code est le taux de lignes de code couvert par des tests.
II. Tester son application
Un test a deux objectifs : valider le fonctionnement d’une partie du système et s’assurer qu’il n’y ait pas de régression tout au long du développement du projet (quand on fait évoluer l’application, cela ne crée pas de bug ailleurs).
Imaginez que l’on vienne de tester les feux de votre voiture. Si à chaque fois que les câbles électriques sont déplacés il faut refaire un test pour s’assurer que le feu fonctionne toujours, c’est extrêmement chronophage. Pire encore, si vous ne testez pas l’allumage de votre feu à chaque modification sur votre voiture, et qu’à la fin l’on s’aperçoit que le feu ne fonctionne pas, il faudra tout reprendre à zéro pour comprendre l’origine du problème. Aujourd’hui, il existe des systèmes qui vont diagnostiquer le passage du courant dans le réseau électrique de la voiture et indiquer sur le tableau de bord la source du problème.De la même façon, il existe des systèmes de vérification similaires dans le développement mobile.
II.1 La bonne stratégie de tests
Il existe différents types de tests dans le développement logiciel. Le vocabulaire associé aux tests peut être différent en fonction des sources sur lesquelles on se base. Nous avons choisi la catégorisation suivante :
- Les tests unitaires vont isoler une partie de l’application et valider son fonctionnement. On s’assure par exemple qu’une ampoule fonctionne avant de l’intégrer dans l'habitacle d’une voiture.
- Les tests fonctionnels permettent la validation de plusieurs briques de notre système dans un contexte d’utilisation parfaitement connu et “normal” (en réduisant les dépendances à d’autres systèmes, en mimant l’interface utilisateur…). Dans notre cas, nous testerons notre feu de voiture branché sur une source de courant fiable (une prise électrique ou sur une batterie de test).
- Les tests d’intégrations permettent la validation d’une fonctionnalité globale. Ils sont exécutés dans un environnement intégré et ont des dépendances à d’autres systèmes potentiellement instables. Par exemple, un test d’intégration validerait l’allumage ou l’extinction du feu de voiture après l’appui sur le bouton du tableau de bord.
- Les tests d’interface utilisateur vont tester la robustesse de l’interface utilisateur face à des formats et longueurs de données variés (textes longs, avec des emojis, images trop grandes,…).
Chaque type de tests ne donne pas le même niveau d’information en cas de réussite ou d’échec. Par exemple, un test d’intégration réussi permet d’être rassuré quant au fonctionnement d’une partie de l’application. S’il échoue, identifier la source du problème devient difficile. En revanche, si un test unitaire remonte une erreur, il sera possible de déterminer avec exactitude la cause du problème. Il faut donc impératif de décider d’une bonne stratégie de tests pour optimiser les ressources employées.
Les tests sont très efficaces pour maintenir un produit de qualité. Cependant ils peuvent être longs à mettre en place et à maintenir. Mike Cohn dans le livre “Succeeding with Agile” décrit une stratégie idéale appelée la pyramide des tests.
L’idée ici est d’investir le plus de temps dans les tests unitaires qui vont rendre la base de votre pyramide la plus stable possible, en validant unitairement le fonctionnement de chaque brique. De ce fait, les autres types de tests seront plus fiables car il utilisent des parties de votre système dont le fonctionnement sera validé par les tests unitaires.
II.2 Cas du mobile
Il est souvent difficile de trouver du temps pour mettre en place des tests.
La pyramide de tests est la stratégie idéale, cependant elle nécessite beaucoup de ressources. Il faut donc adapter votre stratégie de tests pour que celle-ci apporte une réelle plus-value à la fiabilité de l’application.
Les applications mobiles sont des logiciels évolutifs, au gré notamment des nouveaux systèmes d’exploitation. C’est pour cette raison que nous privilégions les tests unitaires et les tests fonctionnels : ceux-ci vous permettent de savoir avec exactitude l’origine d’un dysfonctionnement qui serait induit par une modification de l’application, contrairement à un test d’intégration.
En effet, la plupart des applications mobiles récupèrent des données depuis un serveur pour les afficher à l’utilisateur. La communication mobile-serveur rend les tests d’intégration longs et très sensibles : votre test d’intégration peut échouer car votre serveur n’a pas pu répondre dans le temps imparti. Dans ce cas, votre application ne présente peut-être pas de bug, mais le test échoue quand même et vous passez du temps à déterminer la source du problème.
Les tests d’intégration ne sont pas utilisés pour rendre un système robuste aux changements, mais pour valider le fonctionnement de celui-ci. Coûteux à mettre en place, nous ne les utilisons que pour tester les parcours critiques de nos applications. De plus, chez FABERNOVEL TECHNOLOGIES nous profitons de l'exécution de ces tests pour vérifier que nos apps ne plantent pas lors de la navigation et pour faire des captures d’écrans permettant de vérifier le rendu visuel.
III. L’integration continue
Vous disposez maintenant de tous les systèmes pour que chaque modification effectuée sur votre application n’altère pas sa cohérence au niveau du code et sa stabilité fonctionnelle.
Si l’on reprend l’analogie avec la voiture, il y a à présent une liste de tâches à effectuer lorsque vous ajoutez, supprimez ou modifiez quelque chose afin de s’assurer que tout fonctionne.
La voiture est donc plus fiable, mais sa construction est beaucoup plus longue en raison du temps nécessaire pour procéder aux vérifications. Il faut trouver un moyen d’automatiser ces tâches.
Chez FABERNOVEL nous utilisons GIT pour versionner notre code, ce qui permet de gérer et ordonner les différents changements au niveau du code. Via ce système, tout code est donc l’incrément de petites modifications unitaires successives.
Chaque incrément va déclencher l'exécution de tout ou partie des tâches de vérification sur nos systèmes d’intégration :
- S’assurer que le dossier du projet n’est pas encombré de fichiers inutiles
- Mettre à jour les dépendances du projet
- S’assurer que le projet compile
- Exécuter les tests unitaires et les tests d’interface utilisateur
- Exécuter une analyse statique du code
- Uploader le résultat de ces analyses sur SonarQube
Pour les plus curieux cette automatisation est permise par la mise en place d’un système complet :
- Gerrit : Pour le versionning du Code (et la code-review)
- Jenkins : pour orchestrer les différentes tâches et les assigner aux ordinateurs disponibles
- Fastlane pour définir les workflow de test (ou de livraison). Nous avons développé nos propres surcouches au framework pour rationaliser les processus entre nos différents projets
- SonarQube / Slack : pour la visualisation des résultats
En conclusion, pour construire un produit pérenne il est essentiel de construire un socle stable sur lequel développer sereinement.
Il est nécessaire de définir des règles puis en fonction de votre budget et de vos contraintes planning, une stratégie de test (automatisés ou manuels) pour garantir la stabilité de votre produit, qui est rappelons le l’un des premiers critères d’adoption par vos utilisateurs.