Tests unitaires, méthodes agiles et intégration continue

De manière générale, l’intégration continue (abréviation CI) permet de garantir une plus grande souplesse et une plus grande qualité dans le processus de développement.

Il est la base des méthodes de développement dites « agiles » et fonctionne avec les tests unitaires.

L’article ci-dessous est une vulgarisation du procédé. Les termes ne sont pas à prendre au pied de la lettre, mais la description est exacte.

La base : les tests unitaires

A l’origine, quand on concevait des logiciels ou des sites web, on faisait des tests soi-même. Ce qui est un bon principe, mais qui a une limite. Cette limite se situe entre la chaise et l’ordinateur : l’humain.

Définition du problème

En effet, 3 problèmes majeurs sont ainsi rencontrés :

  • notre mémoire est limitée, nous sommes faillibles et donc il y a un risque d’oubli des tests à effectuer
  • lorsqu’une équipe reprend un projet, il lui est impossible de connaître parfaitement les tests « évidents » (ou du moins qui le paraissent) réalisés par l’équipe initiale
  • même notés dans un cahier, les tests prennent de plus en plus de temps au fur et à mesure du développement s’ils sont réalisés manuellement.

La solution

La solution, ce sont les tests unitaires.

Présents dans la plupart des langages et dans la plupart des frameworks, ces outils permettent de réaliser des tests de manière automatisée.

Certains langages, comme Java et Ruby, intègrent d’ailleurs nativement cette notion de tests unitaires et sont eux-même développés au travers de ce processus.

Ces tests sont partagés avec le code source. Ce qui permet de les exécuter à tout moment.

Fonctionnement

Le principe est assez simple.

L’idée est de tester chaque élément du code. Par exemple, dans Ruby on Rails, on peut tester :

  • les modèles
  • les contrôleurs
  • les vues
  • les routes
  • les requêtes
  • les emails
  • les fonctionnalités
  • les helpers
  • les tâches automatiques
  • l’affichage du HTML et l’exécution du Javascript
  • etc…

Le champs d’application est très vaste.

En gros, pour ainsi dire tout le code peut être testé.

On rédige les processus de tests et on exécute le tout dans un environnement protégé.

Il existe même des outils qui génèrent un contenu « réaliste » des bases de données (nom, prénom, adresse email, adresse postale, mot de passe, lorem ipsum).

Quand tous les tests passent, l’application est – théoriquement – validée.

On peut alors modifier du code. S’il a un impact par ailleurs, on sera immédiatement au courant.

Le développement piloté par les test (TDD)

Une technique de développement s’est développée autour des tests unitaires : le développement piloté par les tests (test driven development – TDD).

Le principe est de concevoir les tests avant de coder. Ce sont les tests qui influencent le code.

Extreme Programming (XP)

L’extreme programming (XP) est une méthode agile qui implique un travail en binôme.

Dans le binôme, l’un rédige les tests et l’autre le code correspondant. En alternant.

Cela garantit une plus grande qualité du code et également une plus grande efficacité.

Scrum

Dans la méthode de développement SCRUM, le développement est orienté via les scenarii à chaque sprint.

Chaque sprint est divisé en tâches (tickets).

Chaque tâche peut être testée grâce à un ou plusieurs tests unitaires.

Le partage de code source

C’est un élément essentiel pour le travail en équipe. Je vais prendre l’exemple de Git. Pour ma part, j’utilise les services de Github, qui offre un accès (très) sécurisé et performant à une plateforme très complète autour de Git. Sans avoir un coût exorbitant.

Chaque développeur possède une copie du code source et peut la synchroniser à tout moment.

Le suivi du projet

Grâce à une plateforme comme Github, on peut gérer une équipe de développeurs, testeurs et autres. L’évolution du projet se voit en temps réel.

Il existe même un gestionnaire de bugs et demandes de fonctionnalité qui peut être synchronisé avec une interface de gestion de projets agiles (SCRUM). Par exemple Waffle.

Cerise sur le gâteau : chaque envoi de modification (commit) permet d’interagir directement avec les tâches du projet. A chaque tâche est donc associée la portion du code concernée et l’auteur de la modification.

Chaque envoi de modification est réversible, le procédé est donc sécurisé.

L’intégration continue (CI)

Résumons.

Nous avons un code dont la qualité peut être testée grâce aux tests unitaires.

Nous avons également une plateforme de partage de code source, qui nous permet de collaborer en se partageant un même code source en toute sécurité.

Le tout est synchronisé avec notre outil de gestion de projets.

Que nous manque-t-il ? Un processus de déploiement sécurisé.

L’intégration continue permet de déployer une application de manière sécurisée en procédant ainsi, à chaque envoi de modifications (push) ou bien manuellement :

  • récupération du code sur le dépôt de code source
  • préparation d’un environnement d’exécution des tests similaire à l’environnement de production
  • exécution des tests unitaires.

A partir de là, 2 cas de figure :

  • des erreurs ont été rencontrées : le processus s’arrête, un rapport est automatiquement envoyé de sorte à réviser le code et refaire soi-même les tests unitaires, sachant que la portion de code critique a été identifiée
  • tout se passe bien : le déploiement de l’application est réalisé.

Une fois le déploiement réalisé, un test final est effectué afin de valider le fait que le site est toujours fonctionnel :

  • tout fonctionne : c’est parfait, tout est terminé.
  • le site ne fonctionne plus : un rapport est envoyé et l’application est rétrogradée à la version précédente.

L’exemple de Heroku + Codeship

J’utilise Heroku comme plateforme d’hébergement de mes applications web, Codeship (sous forme de composant Heroku) pour le processus d’intégration continue et Github pour l’hébergement du code source.

Déjà, je créé un dépôt privé sur Github pour l’application web. J’y mets mon code source qui intègre déjà les tests unitaires.

Ensuite, je créé mon application Heroku.

4 secondes plus tard, j’ajoute le composant Codeship.

1 seconde plus tard, je configure Codeship. (là, c’est un peu plus long, il y en a bien pour 5 minutes)

En gros, je branche Codeship à mon dépôt de code source Github.

Je définis la configuration de la machine de test.

Je définis le processus de déploiement.

Je fais un envoi de code pour tester, et c’est tout. Mon application est prête pour l’intégration continue.

Sachant que l’intégralité de la solution est proposée en mode « cloud » avec des charges proportionnelles à la taille du projet. Au début, ça coûte une bouchée de pain. Et plus il y a de développeurs et/ou de visiteurs, plus ça coûte cher. Tout en restant raisonnable compte tenu de la qualité des produits.

Pour conclure

Les tests unitaires et l’intégration continue sont les garants de projets de qualité.

Ce sont des outils excellents pour suivre les méthodes agiles. Au-delà d’assurer une qualité irréprochable, ils permettent une souplesse maximale au niveau des équipes.

Si les tests sont bien rédigés, et si en plus on utilise un framework structuré (type Ruby on Rails), un développeur peut être intégré sans risque sur un projet qu’il ne connait pas. C’est un atout majeur.

Evidemment, si vous êtes seul développeur et que vous concevez une application web pour gérer votre collection de pins, ne rentrez pas dans un processus qui deviendrait chronophage.

Le tout est de savoir faire la part des choses et utiliser le bon outil au bon moment.

 

Ruby : le développement agile

Chez Morin Innovation, société orienté « startup », nous travaillons à 100 % en mode agile. L’enjeu est de pouvoir être réactif tout en sécurisant le travail. Un bon équilibre en qualité et productivité.

Pour cela, nous nous appuyons sur Ruby, un langage orienté agilité de par sa conception.

Idée reçue n°1 : tout projet peut être mené quel que soit le langage utilisé

Cette idée, souvent répandue par les chefs de projets qui n’ont pas forcément de connaissances techniques, est théoriquement vraie. Mais théoriquement seulement.

Je peux théoriquement construire une maison en bois avec beaucoup d’allumettes. La maison sera théoriquement aussi solide. Mais il sera plus rapide et plus adapté d’utilisé le matériau adéquate.

Pour le développement, c’est pareil. C’est dans cet esprit qu’a été conçu Ruby dès ses origines : pour le développement agile.

Le langage et son environnement permettent de réaliser très rapidement des développements robustes. Dans la limite de ce que permettent les langages de script.

Idée reçue n°2 : les performances du langage déterminent les performances de l’application

Ne confondons pas performances et efficacité. Si l’interpréteur Ruby n’est pas le meilleur (ni le pire) en termes de performances, il permet un développement rapide. Et son environnement lui permet d’être optimal en termes d’efficacité.

Rien ne sert de courir, il faut partir à point.

Ruby propose une syntaxe optimale et minimaliste pour le développement. La relecture en est donc simplifiée. La correction de bugs est plus rapide. L’optimisation est également plus facile car plus simple à appréhender.

Également, le gain de temps obtenu sur la réalisation permet, si nécessaire, de passer du temps sur l’optimisation.

De plus, Ruby possède un système de packages (des composants à installer) qui fournissent du code open-source très simple à intégré et déjà optimisé.

Car, s’il y a une force dans Ruby, c’est également son système de plugins natifs, qui permet d’appeler du code natif (performant) depuis le langage Ruby. Idéal pour les portions de code critique (analyse de contenu XML/HTML, génération d’images, etc).

Enfin, le code Ruby peut être exécuté au travers d’une machine virtuelle Java (JVM) grâce à JRuby, ce qui apporte un gain de performances conséquent tout en profitant de l’efficacité de l’outil.

Ruby en environnement agile

Passées ces idées reçues, venons-en aux faits. En quoi Ruby est-il meilleur qu’un autre en environnement agile (SCRUM ou autres) ?

Développement souple et rapide

Comme évoqué, la syntaxe même de Ruby lui permet de faciliter le travail du développeur, que ce soit seul ou en équipe.

Un petit bout de code « pour tester » peut être très rapidement déployé. En quelques minutes, on peut avoir un outil d’extraction de données ou de quoi tester un service web.

Adapté aux tests unitaires

Que ce soit via ses modules de tests intégrés (Test::Unit) ou via des outils externes plus élaborés (RSpec), les tests unitaires sont une partie intégrante de l’environnement de développement Ruby.

Au delà de l’apport évident en termes de sécurité et de fiabilité du code, c’est un atout majeur pour le travail en équipe et le développement par les tests (Test Driven Development) qui est un des socles du développement agile.

Bien entendu, de nombreux outils permettent une intégration continue optimale et naturelle, ce qui est également un atout des méthodes de travail agiles.

Idéal pour le travail en équipe

Le code étant basé sur le script, son intégration dans les dépôts de code source (Git et autres) est totalement adapté.

Egalement, son système de packages (les gems) et la configuration des dépendances via le fameux Gemfile permettent d’installer rapidement l’environnement de développement adapté au projet.

Egalement, les gems pouvant être privés et stockés sur Git, le partage de code entre les projets est totalement optimisé.

Un atout majeur pour rejoindre les équipes. Que l’on travaille sur un même réseau ou à distance. Et dans un environnement de travail hétérogène.

Ruby on Rails : le développement web agile

Ruby on Rails est un framework web conçu en Ruby.

Son avantage est sa structure, copiée par de nombreux autres projets dans d’autres langages (Symfony, Play! et autres) : mais la copie ne vaut jamais l’original. Cette structure permet non seulement de s’y retrouver dans son propre code, mais permet également de facilement intégrer des développeurs sur un projet. Et, par extension, faciliter la reprise du code.

Ruby on Rails intègre évidemment les tests unitaires, un impératif pour l’intégration continue (via Codeship par exemple).

Le tout en profitant des atouts de Ruby. Et d’une logique orienté efficacité plus que performances.

La souplesse de l’outil permet un déploiement en environnement « scalable » très simple. Notamment grâce à des solutions comme Heroku, qui proposent un hébergement sans ce soucier de l’aspect administration réseau (tout se fait au travers d’un simple dépôt Git sécurisé).

La sécurité est au rendez-vous, avec un respect scrupuleux des recommandations OWASP.

Les performances « effectives » sont également au RDV, grâce – notamment – à des mécanismes de mise en cache simplifiés.

Success Stories

Travaillant en marque blanche (invisible pour le client final), je ne peux présenter les success stories de Morin Innovation. Je peux juste dire que Ruby nous permet de tenir nos délais de manière systématique (sauf rares exceptions indépendantes de l’équipe), tout en ayant une réactivité maximale sur la correction de bugs et avec des performances très honorables. (on a pu monter jusqu’à 1000 utilisateurs simultanés sur un projet web sans blocage ni lenteur)

Plusieurs sociétés se sont développées grâce à Ruby, et plus particulièrement Ruby on Rails :

  • Twitter
  • Shopify
  • LinkedIn
  • GitHub
  • Basecamp
  • Drivy
  • Airbnb
  • et bien d’autres.

Quid des autres solutions ?

D’autres solutions se développent, bien souvent en « copiant » le modèle de Ruby on Rails, mais dans d’autres langages.

NodeJS, par exemple, est à la mode. On entend souvent des responsables de projets informatiques en faire la promo en amont, dire ensuite que c’est génial… mais ne jamais le déployer à grande échelle. CQFD 🙂

En PHP, Symfony s’approche de Ruby on Rails. En Java (Scala), c’est Play qui reprend le même modèle.

Mais bon, la syntaxe joue beaucoup. Le langage Python, et son framework web Django, est probablement un des seuls à être du niveau de Ruby en termes d’efficacité.

L’évolution en environnement Java

Bien souvent, de nombreuses structures se sont orientées vers Java. Et elles souhaitent ensuite évoluer vers des solutions plus agiles.

L’avantages de Ruby est qu’il possède son environnement d’exécution Java : JRuby.

Non seulement JRuby peut s’exécuter dans un environnement Java, mais en plus on peut appeler du code Java depuis JRuby et vice versa.

Et maintenant ?

Je vous ai présenté l’environnement de développement Ruby.

Si vous souhaitez en savoir plus, n’hésitez pas à contacter Morin Innovation.

La cité niortaise devient la cité des startups et de l’innovation. Ce sera un plaisir pour nous de contribuer à l’accélération de vos développements.