Comment démarrer avec l'intégration continue

Intégration continue dans la vie réelle

Tout ce que vous devez savoir pour vous lancer dans l'intégration continue: stratégies de branchement, automatisation des tests, outils et meilleures pratiques.

L'objectif: fournir du code de travail rapidement et en toute sécurité

L’intégration continue a pour objectif de fournir du code à la branche principale de votre référentiel:

  • Rapidement: du nouveau code envoyé au référentiel à sa fusion dans la branche principale quand il fonctionne devrait être fait en quelques minutes
  • En toute sécurité: comment savons-nous que le nouveau code fonctionne? L'intégration continue consiste à configurer les contrôles appropriés pour fusionner le code automatiquement en toute confiance.

L'intégration continue est un peu sur les outils et beaucoup sur l'esprit et la culture de l'équipe. Vous souhaitez que votre processus de développement facilite l'intégration rapide du nouveau code tout en conservant une branche principale opérationnelle à tout moment. Cette branche principale active permettra la distribution continue ou le déploiement continu à l’avenir. Mais ce sont pour un autre post. Concentrons-nous d'abord sur l'intégration continue.

L’intégration continue repose sur 2 piliers:

Travailler en petits morceaux

Imaginez une équipe de 5 personnes travaillant sur un produit SaaS. Chacun d’entre eux développe une nouvelle fonctionnalité distincte. La charge de travail de chaque fonction est d'environ 1 à 2 semaines. Il y a 2 façons d'aller ici.

  • L'équipe pourrait aller avec les branches de fonctionnalités. Chaque développeur travaillera de son côté sur une «branche de fonctionnalité». Les branches seront fusionnées avec la branche principale une fois que tout le monde sera satisfait de son travail
  • L’équipe peut aller avec des branches (encore) mais intégrer son travail à la branche principale à chaque poussée. Même si les choses sont encore un travail en cours! Le travail en cours resterait invisible pour tout utilisateur final ou testeur de la branche principale

Selon vous, quelle approche fonctionnera le mieux?

La première approche conduira finalement au «syndrome de libération imprévisible». Les longues branches de fonctionnalités créent un faux sentiment de sécurité et de confort pour chaque développeur individuellement. Comme les branches se séparent pendant une longue période, il n’ya aucun moyen de mesurer à quel point il sera difficile de tout fusionner. Au mieux, des conflits mineurs de code vont survenir, au pire, les hypothèses de conception fondamentales seront remises en question et les choses devront être retravaillées… à la dure.

Les retouches se feront sous la pression du temps, ce qui entraînera une baisse de qualité et une accumulation de dette technique. C'est un cercle vicieux.

Voir le post sur Pourquoi vous ne devriez pas utiliser les branches de fonctionnalités pour les détails incorrects.

La deuxième approche est ce dont nous avons besoin pour permettre une intégration continue. Chaque développeur travaille sur sa propre branche. La différence est:

Les modifications sont fusionnées dans la branche principale à chaque poussée, et chaque développeur synchronise sa branche avec la dernière version de la branche principale plusieurs fois par jour.

De cette façon, l'équipe peut résoudre les conflits et aligner les hypothèses de conception plus rapidement et plus facilement. 5 petits problèmes découverts tôt sont bien meilleurs qu'un gros problème découvert juste avant la sortie. Consultez la section «Basculement de fonctionnalités» ci-dessous pour voir comment intégrer «travaux en cours» à la branche principale.

La sécurité vient avec des contrôles automatisés

Le processus de développement de logiciel ancien était basé sur un cycle de construction suivi d'un cycle de test. Et cela correspondrait probablement encore à l’approche des «branches fonctionnelles» décrite ci-dessus. Si nous intégrons et fusionnons du code des dizaines de fois par jour, le test manuel n’a aucun sens. Cela prendrait trop de temps. Nous avons besoin de contrôles automatisés pour vérifier que le code fonctionne correctement. Nous avons besoin d’un outil de CI qui prend en charge l’opération de chaque développeur et exécute automatiquement la construction et les tests.

Le type et le contenu du test doivent être:

  • assez rapide pour fournir des commentaires au développeur en quelques minutes
  • suffisamment minutieux pour fusionner le code avec la branche principale en toute confiance

Malheureusement, il n'y a pas de taille unique pour tous les types et contenus de tests. Le bon équilibre est spécifique à votre projet. N'exécutez pas de suites de tests volumineuses et prenant beaucoup de temps au cours de votre phase de CI. De tels tests offrent une meilleure sécurité, mais ils entraînent un retour d’information différé aux développeurs. Cela conduit à un changement de contexte qui est une pure perte de temps.

Optimiser le temps des développeurs et réduire le changement de contexte

Les longues vérifications de CI, et par plus de 3 minutes, créent une perte de temps composée pour chaque développeur de votre équipe. Comparons un «bon» et un «mauvais» flux de travail.

Le «bon» flux de travail:

  • Vous commettez et poussez votre code
  • Le build et les tests de CI durent de 1 à 3 minutes
  • Pendant les 1 à 3 minutes que vous passez en revue la tâche à accomplir, modifiez le statut de l'outil de gestion ou relisez votre code
  • En 3 minutes, vous obtenez un statut réussi: vous pouvez passer à la partie suivante de votre tâche. Si vous obtenez une version échouée: vous pouvez résoudre le problème immédiatement

Le «mauvais» flux de travail:

  • Vous commettez et poussez votre code
  • La construction et les tests de CI durent 15 minutes
  • Que faites-vous pendant ces 15 minutes?
  • Vous pourriez prendre une tasse de café avec l'équipe. Assez bien, mais combien pouvez-vous en avoir par jour?
  • Vous auriez probablement votre tête sur la prochaine tâche de votre pipeline
  • 15 minutes plus tard, vous recevez une notification de génération ayant échoué. Vous devez revenir à la tâche précédente, essayer de résoudre le problème… et opter pour une autre boucle de 15 minutes…
  • À ce stade, vous vous demandez: devrais-je revenir à la tâche suivante ou tout simplement attendre les 15 minutes et me rassurer que ma tâche actuelle est vraiment terminée…

Le mauvais flux de travail n'est pas seulement une perte de temps. C'est aussi frustrant pour les développeurs. Et les développeurs productifs sont des développeurs heureux.

Vous devez aligner vos outils et vos workflows pour satisfaire vos développeurs.

Outils

Ramification

L’intégration continue consiste à intégrer le code de différentes branches de développeur à une branche commune de votre système de gestion de la configuration. Il y a de fortes chances que vous utilisiez git. Dans git, la branche principale par défaut dans un référentiel est appelée "maître". Certaines équipes créent une branche appelée «develop» en tant que branche principale de l'intégration continue. Ils utilisent «maître» pour suivre les livraisons et les déploiements (développement étant fusionné pour maître).

Vous avez probablement déjà une branche principale sur laquelle votre équipe insère ou fusionne le code. S'en tenir à cela.

Chaque développeur doit travailler sur sa propre branche. On peut utiliser plusieurs branches si on travaille sur plusieurs sujets en même temps. Bien que ce soit au mieux un signe de travail «flou». Dès qu'une partie cohérente de votre code est prête, poussez votre référentiel. Les vérifications d'éléments de configuration entreront en vigueur et fusionneront votre code dans la branche principale s'ils réussissent. Si les vérifications échouent, vous êtes toujours sur votre propre branche et pouvez corriger ce qui doit être corrigé et appuyez à nouveau.

Le mot important dans le processus ci-dessus est une partie cohérente de votre code. Comment savez-vous qu'il est cohérent? Simple.

Si vous pouvez facilement trouver un bon message de validation, il est cohérent.

Par contre, si votre message de validation nécessite 3 puces et de nombreux adjectifs et adverbes, il n’est probablement pas bon. Divisez votre travail en plusieurs commits cohérents. Et puis poussez le code. Des révisions cohérentes du code d'aide des commits facilitent le suivi de l'historique du référentiel.

Ne poussez rien, c’est la fin de la journée!

Demandes de tirage

Qu'est-ce qu'une demande d'extraction? Une demande de tirage est un concept dans lequel vous demandez à l'équipe de fusionner votre branche avec la branche principale. L'acceptation de votre demande doit passer par un statut fourni par votre outil de CI et éventuellement par une révision du code. En fin de compte, l'acceptation manuelle d'un être humain chargé de fusionner les demandes d'attraction.

Les demandes d'extraction sont nées dans des projets open source. Les responsables de la maintenance avaient besoin d'un moyen structuré d'évaluer les contributions avant de les fusionner. Les demandes d'extraction ne font pas partie de Git. Ils sont cependant pris en charge par tous les fournisseurs Git (GitHub, BitBucket, GitLab,…).

Notez que les demandes d'extraction ne sont pas obligatoires pour l'intégration continue. Leur principal avantage est de prendre en charge un processus de révision de code, qui ne peut pas être automatisé par la conception.

Si vous utilisez des demandes d'extraction, les mêmes principes de "travail en petites unités" et "d'optimisation du temps des développeurs" s'appliquent:

  • gardez chaque demande de tir petite et avec un but clair (cela facilitera la révision du code)
  • gardez vos chèques CI rapidement

Contrôles automatisés

Le cœur de votre processus continu est constitué par les contrôles automatisés. Ils s'assurent que le code de la branche principale fonctionne correctement après la fusion de votre code. S'ils échouent, votre code ne sera pas fusionné. Au minimum, le code doit être compilé ou transpilé ou quoi que votre pile technologique fasse pour le rendre prêt pour l'exécution.

En plus de la compilation, vous devez exécuter des tests automatisés pour vous assurer que le logiciel fonctionne correctement. Plus la couverture de test est bonne, plus vous pouvez avoir confiance dans la fusion du nouveau code avec la branche principale. Attention cependant! Une meilleure couverture signifie plus de tests et un temps d'exécution plus long. Vous devez trouver le bon compromis.

Où commencez-vous lorsque vous n'avez pas du tout de tests ou que vous avez besoin de réduire certains tests de longue durée? Concentrez-vous sur ce qui est essentiel pour votre projet ou produit.

Si vous construisez une application SaaS, vous devez vérifier que les utilisateurs peuvent s'inscrire ou se connecter et effectuer les opérations les plus élémentaires fournies par votre SaaS. Sauf si vous développez un concurrent Salesforce, vous devriez pouvoir exécuter vos tests en quelques minutes, voire quelques secondes. Si vous construisez un backend de traitement de données lourd: utilisez des ensembles de données limités pour exercer les différents blocs de construction. Continuez à courir longtemps sur des ensembles de données volumineux en dehors de l'intégration continue. Des tests longs peuvent être déclenchés après la fusion du code.

Astuces Pro

Fonctionnalités

Le concept clé de l'intégration continue est de mettre votre code dans la branche principale le plus rapidement possible. Même travaux en cours. Même les fonctionnalités qui ne fonctionnent pas complètement ou que vous ne voulez pas exposer aux testeurs ou aux utilisateurs finaux. Le moyen d'y parvenir est d'utiliser les fonctions bascules. Avoir votre nouvelle fonctionnalité sous une bascule activé / désactivé. La bascule peut être un indicateur booléen lors de la compilation, une variable d'environnement ou un élément d'exécution. La bonne approche dépend de ce que vous voulez réaliser.

Le premier avantage majeur des fonctionnalités est que vous pouvez les mettre en production et activer / désactiver la nouvelle fonctionnalité en cas de besoin. Vous pouvez redémarrer un serveur avec une variable d'environnement modifiée ou activer / désactiver une nouvelle disposition de tableau de bord d'interface utilisateur. De cette façon, vous avez toute latitude pour déployer la fonctionnalité. Ou désactivez-le si cela provoque des problèmes inattendus dans la production. Ou autoriser les utilisateurs finaux à activer ou désactiver cette fonctionnalité (en cas de basculement de l'interface utilisateur).

Le deuxième avantage majeur des fonctionnalités est qu'elles vous obligent à penser à la limite entre ce que vous faites et le code existant. C’est un bon exercice et c’est avec quoi vous devriez commencer quand vous apportez un ajout à un système existant. L'étape de basculement des fonctions rend cette étape du processus plus visible.

Le seul inconvénient des options est que vous devez les nettoyer périodiquement de l'environnement et du code. Une fois qu'une fonctionnalité est testée au combat et adoptée par les utilisateurs, elle devrait être la valeur par défaut. Le code pour la bascule et l'ancienne version de choses (le cas échéant) doivent être nettoyés. Ne tombez pas dans le piège d’un système de «configuration en tant que bascule». Le piège est que vous ne serez jamais en mesure de maintenir et de tester toutes les combinaisons de bascules et que vous aurez une architecture fragile à la fin.

Gardez le temps de construction de CI moins de 3 minutes

Rappelez-vous le «bon» et le «mauvais» flux de travail dans la première partie de l'article. Nous voulons éviter le changement de contexte pour les développeurs. Choisissez votre téléphone et définissez une minuterie de 3 minutes. Voyez combien de temps il vous reste à attendre quelque chose! 3 minutes devraient être un maximum absolu pour vous permettre de vous concentrer et de vous déplacer de manière sûre et efficace d'une tâche à l'autre.

Une construction en moins de 3 minutes peut paraître folle à certaines équipes, mais elle est tout à fait réalisable. Cela a plus à voir avec la façon dont vous organisez votre travail que les outils que vous utilisez. Les moyens d'optimiser votre construction sont les suivants:

  • Utilisez davantage de capacités de génération: si vous ne disposez pas de plusieurs versions simultanées de votre outil de configuration et que les générations sont mises en file d'attente, les développeurs perdent du temps
  • Tirer parti de la mise en cache: la plupart des piles technologiques nécessitent l’installation et la configuration de dépendances lors de l’exécution d’une nouvelle version. Votre outil de configuration doit pouvoir mettre en cache ces étapes lorsque les dépendances ne changent pas afin d'optimiser le temps de génération
  • Passez en revue vos tests: vérifiez que vos tests sont optimisés pour le temps. Supprimez les délais d'attente et les longues étapes d'attente en toute sécurité. Si vous devez exécuter des suites de tests volumineuses, envisagez de les déplacer dans une version distincte exécutée après la fusion vers la branche principale. Ils ne feraient plus partie de la sauvegarde de l'intégration continue, mais des tests lourds ne devraient pas l'être de toute façon.
  • Séparez votre base de code: vous devez tout avoir dans un référentiel? Devez-vous créer et exécuter des tests sur tout, même lorsque de petites pièces changent? Il pourrait y avoir des victoires ici.
  • Exécuter les tests de manière conditionnelle: exécutez vos tests uniquement si certains répertoires ont été modifiés. Si votre base de code est bien organisée, cela peut être une énorme victoire
Le fait d’imposer une limite de temps limitée à vos vérifications d’instruments de confiance exige que vous amélioriez fondamentalement tout votre processus de développement.

Comme Jim Rohn l'a dit:

“Devenez millionnaire non pour le million de dollars, mais pour ce que vous ferez pour l'atteindre”.

La fusion virtuelle: votre code seul n'a pas d'importance

La plupart des outils d'intégration continue exécutent la génération de CI sur votre branche pour indiquer si elle peut être fusionnée ou non. Mais ce n’est pas ce qui est intéressant ici. Si vous savez ce que vous faites, il y a de bonnes chances que le code que vous venez de lancer fonctionne déjà! Votre outil de CI est censé vérifier que votre branche fusionnée avec la branche principale fonctionne correctement.

Votre outil de configuration doit effectuer une fusion locale de votre branche avec la branche principale, puis exécuter la construction et les tester. Votre branche peut alors être automatiquement fusionnée si la branche principale ne change pas entre-temps. Si cela change, les contrôles de CI doivent être exécutés à nouveau jusqu'à ce que votre code puisse être fusionné en toute sécurité. Si vos outils de CI ne prennent pas en charge ce type de flux de travail, modifiez-le.

Le gestionnaire de tâches diaboliques

Il est faux de croire qu'il est intéressant de pouvoir suivre le code associé à une tâche dans votre tableau Agile ou votre système de suivi des bugs, comme JIRA ou similaire. Bien que ce soit un bon concept sur le papier, l’impact sur le processus de développement n’en vaut certainement pas la peine. Le gestionnaire de tâches fournit une vue du monde «caractéristiques et bugs». Le code est structuré et superposé d'une manière très différente. Essayer de réconcilier un élément dans le gestionnaire de tâches et un ensemble de validations est inutile. Si vous voulez savoir pourquoi un morceau de code a été écrit, vous devriez pouvoir obtenir les informations à partir du contexte du code et des commentaires.

Dernières pensées

Les outils ne sont que des outils. La configuration des outils prend probablement une heure. Si vous les utilisez mal, vous n'obtiendrez pas les résultats escomptés.

Gardez à l'esprit les objectifs que nous nous sommes fixés pour l'intégration continue:

  • Fournir du code de travail rapidement et en toute sécurité
  • Optimiser le temps des développeurs et réduire le changement de contexte

La vraie affaire consiste à changer votre état d'esprit pour «continuellement apporter de la valeur» à votre projet ou produit.

Pensez à votre processus de développement logiciel comme une installation de production matérielle. Le code du développeur représente les pièces mobiles. La branche principale est le produit assemblé.

Plus vous intégrez rapidement les différentes pièces et vérifiez que tout fonctionne correctement, plus vous êtes près d'avoir un produit fonctionnel à la fin.

Quelques exemples pratiques:

  • Vous travaillez sur une nouvelle fonctionnalité et devez modifier un composant de bas niveau que d'autres utiliseront probablement. Effectuez un commit dédié à cette partie du composant commun et faites-le déjà fusionner. Continuez ensuite à travailler sur le reste de votre fonctionnalité. Les autres développeurs pourront immédiatement baser leur travail sur votre changement.
  • Vous travaillez sur une fonctionnalité importante qui nécessitera beaucoup de temps et de code? Utilisez une fonctionnalité bascule. Ne travaillez pas en vase clos. Déjà!
  • Vous attendez une révision du code mais personne n’est disponible pour le faire. Si votre code réussit les contrôles de CI, il vous suffit de le fusionner pour que la révision du code ait lieu ultérieurement. Si cela ressemble à briser le processus, rappelez-vous que «faire est meilleur que parfait». Si cela fonctionne, cela donne plus de valeur dans la branche principale que de rester sur le côté pendant des jours.

Merci d'avoir lu! Si vous trouvez l'article utile, cliquez sur le bouton ci-dessous. Cela signifierait beaucoup pour moi et aiderait d'autres personnes à voir l'histoire!

Article initialement publié sur fire.ci le 9 avril 2019.