{"id":31304,"date":"2019-10-31T21:40:34","date_gmt":"2019-10-31T18:40:34","guid":{"rendered":"https:\/\/prohoster.info\/blog\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\/"},"modified":"2019-10-31T21:40:34","modified_gmt":"2019-10-31T18:40:34","slug":"evolyutsiya-ci-v-komande-mobilnoj-razrabotki","status":"publish","type":"post","link":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","title":{"rendered":"\u00c9volution du CI dans l'\u00e9quipe de d\u00e9veloppement mobile.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Aujourd'hui, la plupart des produits logiciels sont d\u00e9velopp\u00e9s en \u00e9quipes. Les conditions de succ\u00e8s du d\u00e9veloppement en \u00e9quipe peuvent \u00eatre repr\u00e9sent\u00e9es sous la forme d'un sch\u00e9ma simple.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nApr\u00e8s avoir \u00e9crit le code, vous devez vous assurer qu'il :<\/p>\n<ol>\n<li>Fonctionne.<\/li>\n<li>Ne casse rien, y compris le code \u00e9crit par vos coll\u00e8gues.<\/li>\n<\/ol>\n<p>\nSi ces deux conditions sont remplies, vous \u00eates sur la voie du succ\u00e8s. Pour v\u00e9rifier facilement ces conditions et ne pas d\u00e9vier d'une voie avantageuse, l'int\u00e9gration continue a \u00e9t\u00e9 con\u00e7ue.<\/p>\n<p>L'int\u00e9gration continue (CI) est un processus de travail o\u00f9 vous int\u00e9grez votre code dans le code principal du produit aussi souvent que possible. Et pas seulement l'int\u00e9grez, mais v\u00e9rifiez \u00e9galement constamment que tout fonctionne. Comme il y a beaucoup \u00e0 v\u00e9rifier et souvent, il vaut la peine de penser \u00e0 l'automatisation. Vous pouvez tout v\u00e9rifier manuellement, mais ce n'est pas recommand\u00e9, et voici pourquoi.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Les gens sont chers<\/strong>. Une heure de travail de tout programmeur co\u00fbte plus cher qu'une heure de travail de n'importe quel serveur.<\/li>\n<li><strong>Les gens font des erreurs<\/strong>. Par cons\u00e9quent, il peut se produire des situations o\u00f9 des tests sont lanc\u00e9s sur la mauvaise branche ou o\u00f9 un mauvais commit est assembl\u00e9 pour les testeurs.<\/li>\n<li><strong>Les gens sont paresseux<\/strong>. Parfois, quand je termine une t\u00e2che, je me dis : \u00ab Pourquoi v\u00e9rifier ? J'ai \u00e9crit deux lignes, \u00e7a fonctionne s\u00fbrement ! \u00bb Je pense que certains d'entre vous ont aussi ces pens\u00e9es de temps en temps. Mais il est toujours n\u00e9cessaire de v\u00e9rifier.<\/li>\n<\/ul>\n<p>\nComment l'int\u00e9gration continue a \u00e9t\u00e9 mise en \u0153uvre et d\u00e9velopp\u00e9e dans l'\u00e9quipe de d\u00e9veloppement mobile d'Avito, comment on est pass\u00e9 de 0 \u00e0 450 builds par jour, et ce que les machines de build assemblent pendant 200 heures par jour, raconte Nikolai Nesterov (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/nnesterov\/\" class=\"user_link\">nnesterov<\/a><\/noindex>) \u2014 participant \u00e0 tous les changements \u00e9volutifs des CI\/CD de l'application Android.<\/p>\n<p>Le r\u00e9cit est bas\u00e9 sur l'exemple de l'\u00e9quipe Android, mais la plupart des approches sont \u00e9galement applicables \u00e0 iOS.<\/p>\n<p><center><div class=\"youtube-placeholder\" data-id=\"lz8MNATTUCU\" onclick=\"loadVideo(this)\">\r\n        <img decoding=\"async\" src=\"https:\/\/img.youtube.com\/vi\/lz8MNATTUCU\/hqdefault.jpg\" alt=\"Lire la vid\u00e9o\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\nIl y a longtemps, dans l'\u00e9quipe Android d'Avito, il travaillait une seule personne. Par d\u00e9finition, il n'avait besoin de rien en mati\u00e8re d'int\u00e9gration continue : il n'y avait personne avec qui s'int\u00e9grer.<\/p>\n<p>Mais l'application a grandi, de plus en plus de nouvelles t\u00e2ches apparaissaient, et par cons\u00e9quent, l'\u00e9quipe s'est agrandie. \u00c0 un certain moment, il est devenu essentiel de formaliser davantage le processus d'int\u00e9gration du code. Il a \u00e9t\u00e9 d\u00e9cid\u00e9 d'utiliser le Git flow.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/f433effc5ab5a59de3d6bd20a7a87057.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLe concept de Git flow est bien connu : dans un projet, il y a une branche principale develop, et pour chaque nouvelle fonctionnalit\u00e9, les d\u00e9veloppeurs cr\u00e9ent une branche distincte, y effectuent des commits, poussent, et lorsqu'ils souhaitent int\u00e9grer leur code dans la branche develop, ils ouvrent une pull request. Pour favoriser l'\u00e9change de connaissances et discuter des approches, nous avons introduit le code review, ce qui signifie que les coll\u00e8gues doivent v\u00e9rifier et valider le code des autres.<\/p>\n<h2>V\u00e9rifications<\/h2>\n<p>\nRegarder le code \u00e0 travers les yeux d'un autre est g\u00e9nial, mais ce n'est pas suffisant. C'est pourquoi des v\u00e9rifications automatiques sont mises en place.<\/p>\n<ul>\n<li>Tout d'abord, nous v\u00e9rifions <strong>la construction ARC<\/strong>.<\/li>\n<li>Beaucoup <strong>de tests Junit<\/strong>.<\/li>\n<li><strong>Nous calculons la couverture de code<\/strong>, puisque nous lan\u00e7ons des tests.<\/li>\n<\/ul>\n<p>\nPour comprendre comment ces v\u00e9rifications doivent \u00eatre lanc\u00e9es, examinons le processus de d\u00e9veloppement chez Avito.<\/p>\n<p>Sch\u00e9matiquement, on peut le repr\u00e9senter ainsi :<\/p>\n<ul>\n<li>Le d\u00e9veloppeur \u00e9crit du code sur son ordinateur portable. Il peut lancer des v\u00e9rifications d'int\u00e9gration directement ici \u2014 soit par hook de commit, soit en ex\u00e9cutant simplement des v\u00e9rifications en arri\u00e8re-plan.<\/li>\n<li>Apr\u00e8s que le d\u00e9veloppeur ait pouss\u00e9 le code, il ouvre une pull request. Pour que son code soit int\u00e9gr\u00e9 dans la branche develop, il doit passer le code review et obtenir un nombre suffisant de validations. Il est possible d'activer les v\u00e9rifications et les builds ici : tant que tous les builds ne sont pas r\u00e9ussis, la pull request ne peut pas \u00eatre fusionn\u00e9e.<\/li>\n<li>Une fois la pull request fusionn\u00e9e et le code int\u00e9gr\u00e9 dans develop, on peut choisir un moment opportun : par exemple, la nuit, lorsque tous les serveurs sont disponibles, et ex\u00e9cuter autant de v\u00e9rifications que possible.<\/li>\n<\/ul>\n<p>\nLancer des v\u00e9rifications sur son ordinateur portable n'a plu \u00e0 personne. Lorsqu'un d\u00e9veloppeur a termin\u00e9 une fonctionnalit\u00e9, il veut la pousser rapidement et ouvrir une pull request. Si, \u00e0 ce moment-l\u00e0, des v\u00e9rifications longues sont lanc\u00e9es, cela est non seulement d\u00e9sagr\u00e9able, mais cela ralentit aussi le d\u00e9veloppement : tant que l'ordinateur portable effectue des v\u00e9rifications, il est impossible de travailler normalement.<\/p>\n<p>Lancer des v\u00e9rifications la nuit nous a beaucoup plu, car il y a beaucoup de temps et de serveurs, on peut en profiter. Mais, malheureusement, une fois que le code de la fonctionnalit\u00e9 a \u00e9t\u00e9 int\u00e9gr\u00e9 dans develop, le d\u00e9veloppeur a d\u00e9j\u00e0 beaucoup moins de motivation pour corriger les erreurs que le CI a d\u00e9tect\u00e9es. Je me surprenais parfois \u00e0 penser, en consultant le rapport du matin sur toutes les erreurs trouv\u00e9es, que je ferais les corrections plus tard, car il y a une super nouvelle t\u00e2che dans Jira que j'ai envie de commencer d\u00e8s que possible.<\/p>\n<p>Si les v\u00e9rifications bloquent la pull request, il y a suffisamment de motivation, car tant que les builds ne sont pas au vert, le code ne pourra pas \u00eatre int\u00e9gr\u00e9 dans develop, et donc la t\u00e2che ne sera pas termin\u00e9e.<\/p>\n<p>En fin de compte, nous avons choisi une strat\u00e9gie : la nuit, nous ex\u00e9cutons autant de v\u00e9rifications que possible, et les plus critiques, et surtout les plus rapides, sont lanc\u00e9es sur le pull request. Mais nous ne nous arr\u00eatons pas l\u00e0 \u2014 nous optimisons \u00e9galement la vitesse des v\u00e9rifications afin de les transf\u00e9rer du mode nocturne aux v\u00e9rifications sur pull request.<\/p>\n<p>\u00c0 ce moment-l\u00e0, toutes nos compilations passaient assez rapidement, donc nous avons simplement ajout\u00e9 un bloqueur \u00e0 la compilation du pull request : ARK, tests Junit et calcul de la couverture de code. Nous avons activ\u00e9 cela, r\u00e9fl\u00e9chi \u2014 et avons renonc\u00e9 \u00e0 la couverture de code, car nous avons estim\u00e9 que nous n'en avions pas besoin.<\/p>\n<p><strong><em>Nous avons mis deux jours pour configurer le CI de base (ici et par la suite, l'\u00e9valuation temporelle est approximative, n\u00e9cessaire pour le contexte). <\/em><\/strong><\/p>\n<p>Apr\u00e8s cela, nous avons commenc\u00e9 \u00e0 r\u00e9fl\u00e9chir : v\u00e9rifions-nous correctement ? Est-ce que nous lan\u00e7ons les builds sur le pull request de mani\u00e8re ad\u00e9quate ?<\/p>\n<p>Nous lancions la compilation sur le dernier commit de la branche d'o\u00f9 \u00e9tait ouvert le pull request. Mais les v\u00e9rifications de ce commit ne montrent que le code que le d\u00e9veloppeur a \u00e9crit fonctionne. Elles ne prouvent pas qu'il n'a rien cass\u00e9. En r\u00e9alit\u00e9, il faut v\u00e9rifier l'\u00e9tat de la branche develop apr\u00e8s l'int\u00e9gration de la fonctionnalit\u00e9.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPour cela, nous avons \u00e9crit un simple script bash. <strong>premerge.sh :<\/strong><\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n\nset -e\n\ngit fetch origin develop\n\ngit merge origin\/develop<\/code><\/pre>\n<p>\nIci, nous r\u00e9cup\u00e9rons simplement tous les changements les plus r\u00e9cents de develop et les fusionnons dans la branche actuelle. Nous avons ajout\u00e9 le script premerge.sh comme premi\u00e8re \u00e9tape de tous les builds et avons commenc\u00e9 \u00e0 v\u00e9rifier exactement ce que nous voulions, c'est-\u00e0-dire <strong>l'int\u00e9gration<\/strong>.<\/p>\n<p><strong><em>Pour localiser les probl\u00e8mes, trouver des solutions et r\u00e9diger ce script, nous avons mis trois jours.<\/em><\/strong><\/p>\n<p>L'application \u00e9voluait, de plus en plus de t\u00e2ches apparaissaient, l'\u00e9quipe grandissait, et premerge.sh a parfois commenc\u00e9 \u00e0 nous poser probl\u00e8me. Des modifications conflictuelles p\u00e9n\u00e9traient dans develop, ce qui cassait la compilation.<\/p>\n<p>Voici un exemple de comment cela se produit :<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDeux d\u00e9veloppeurs commencent en m\u00eame temps \u00e0 travailler sur les fonctionnalit\u00e9s A et B. Le d\u00e9veloppeur de la fonctionnalit\u00e9 A d\u00e9couvre dans le projet une fonction inutilis\u00e9e <code>answer()<\/code> et, bon scout, la supprime. En m\u00eame temps, le d\u00e9veloppeur de la fonctionnalit\u00e9 B dans sa branche ajoute un nouvel appel \u00e0 cette fonction.<\/p>\n<p>Les d\u00e9veloppeurs terminent leur travail et ouvrent en m\u00eame temps leur pull request. Les builds se lancent, premerge.sh v\u00e9rifie les deux pull requests par rapport \u00e0 l'\u00e9tat frais de develop \u2014 tous les tests sont verts. Ensuite, le pull request de la fonctionnalit\u00e9 A est fusionn\u00e9, le pull request de la fonctionnalit\u00e9 B est fusionn\u00e9\u2026 Boom ! Develop casse, car le code de develop contient un appel \u00e0 une fonction inexistante.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuand develop ne compile pas, cela <strong>d\u00e9sastre local<\/strong>. Toute l'\u00e9quipe ne peut rien recueillir et soumettre pour test.<\/p>\n<p>Il se trouve que je m'occupais le plus souvent de t\u00e2ches d'infrastructure : analytique, r\u00e9seau, bases de donn\u00e9es. C'est-\u00e0-dire que c'\u00e9tait moi qui \u00e9crivais les fonctions et classes utilis\u00e9es par d'autres d\u00e9veloppeurs. En cons\u00e9quence, je me suis souvent retrouv\u00e9 dans ce genre de situations. J'avais m\u00eame une image qui tra\u00eenait un moment.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nComme cela ne nous convenait pas, nous avons commenc\u00e9 \u00e0 r\u00e9fl\u00e9chir \u00e0 des options pour pr\u00e9venir cela.<\/p>\n<h2>Comment ne pas casser develop<\/h2>\n<p>\nPremi\u00e8re option : <strong>recompiler toutes les pull requests lors de la mise \u00e0 jour de develop. <\/strong>Si dans notre exemple la pull request avec la fonctionnalit\u00e9 A arrive en premier dans develop, la pull request de la fonctionnalit\u00e9 B sera recompil\u00e9e, et ainsi, les v\u00e9rifications \u00e9choueront \u00e0 cause d'une erreur de compilation.<\/p>\n<p>Pour comprendre combien de temps cela prendra, regardons un exemple avec deux PR. Ouvrons deux PR : deux builds, deux lancements de v\u00e9rifications. Apr\u00e8s que la premi\u00e8re PR soit int\u00e9gr\u00e9e dans develop, la seconde doit \u00eatre recompil\u00e9e. Au total, pour deux PR, il faut trois lancements de v\u00e9rifications : 2 + 1 = 3.<\/p>\n<p>En principe, c'est normal. Mais nous avons regard\u00e9 les statistiques et la situation typique dans notre \u00e9quipe \u00e9tait d'avoir 10 PR ouvertes, ce qui signifie que le nombre de v\u00e9rifications est la somme d'une progression : 10 + 9 +\u2026 + 1 = 55. Donc, pour accepter 10 PR, il faut recompiler 55 fois. Et cela dans une situation id\u00e9ale, o\u00f9 toutes les v\u00e9rifications passent du premier coup, o\u00f9 personne n'ouvre de pull request suppl\u00e9mentaire pendant le traitement de cette dizaine.<\/p>\n<p>Imaginez-vous en tant que d\u00e9veloppeur, \u00e0 qui il faut appuyer sur le bouton \u00ab merge \u00bb en premier, parce que si c'est le voisin qui le fait, il faudra attendre que toutes les compilations passent \u00e0 nouveau... Non, \u00e7a ne va pas, cela ralentira s\u00e9rieusement le d\u00e9veloppement.<\/p>\n<p>Deuxi\u00e8me m\u00e9thode possible : <strong>compiler les pull requests apr\u00e8s la r\u00e9vision du code. <\/strong>C'est-\u00e0-dire que vous ouvrez la pull request, recueillez le nombre n\u00e9cessaire d'approbations de la part des coll\u00e8gues, corrigez ce qui doit \u00eatre corrig\u00e9, apr\u00e8s cela vous lancez les builds. S'ils r\u00e9ussissent, la pull request est fusionn\u00e9e avec develop. Dans ce cas, il n'y a pas de red\u00e9marrages suppl\u00e9mentaires, mais cela ralentit consid\u00e9rablement le retour d'information. En tant que d\u00e9veloppeur, lorsque j'ouvre une pull request, je veux imm\u00e9diatement voir si elle se compile. Par exemple, si un test \u00e9choue, il faut le r\u00e9parer rapidement. Dans le cas d'une compilation diff\u00e9r\u00e9e, le retour d'information est ralenti, ce qui ralentit toute la production. Cela ne nous convenait pas non plus.<\/p>\n<p>Au final, seule la troisi\u00e8me option reste \u2014 <strong>r\u00e9inventer la roue<\/strong>. Tout notre code, tous nos fichiers sources sont stock\u00e9s dans un d\u00e9p\u00f4t sur le serveur Bitbucket. Par cons\u00e9quent, nous avons d\u00fb d\u00e9velopper un plugin pour Bitbucket.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nCe plugin red\u00e9finit le m\u00e9canisme de fusion des pull requests. Le d\u00e9but est standard : un PR est ouvert, toutes les builds sont lanc\u00e9es, et une r\u00e9vision de code a lieu. Mais une fois que la r\u00e9vision de code est pass\u00e9e et que le d\u00e9veloppeur d\u00e9cide de cliquer sur \u00ab merge \u00bb, le plugin v\u00e9rifie \u00e0 quel \u00e9tat du d\u00e9veloppement les tests ont \u00e9t\u00e9 lanc\u00e9s. Si le d\u00e9veloppement a \u00e9t\u00e9 mis \u00e0 jour apr\u00e8s les builds, le plugin n'autorisera pas la fusion de cette pull request dans la branche principale. Il red\u00e9marrera simplement les builds par rapport au d\u00e9veloppement actuel.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDans notre exemple avec les modifications conflictuelles, de telles builds \u00e9choueront en raison d'une erreur de compilation. Par cons\u00e9quent, le d\u00e9veloppeur de la fonctionnalit\u00e9 B devra corriger le code, relancer les tests, et alors le plugin appliquera automatiquement le pull request.<\/p>\n<p>Avant l'impl\u00e9mentation de ce plugin, nous avions en moyenne 2,7 lancements de tests par pull request. Avec le plugin, cela est pass\u00e9 \u00e0 3,6 lancements. Cela nous convenait.<\/p>\n<p>Il convient de noter que ce plugin a un inconv\u00e9nient : il relance la build uniquement une fois. Il existe donc une petite fen\u00eatre pendant laquelle des modifications conflictuelles peuvent p\u00e9n\u00e9trer dans develop. Mais la probabilit\u00e9 de cela est faible, et nous avons accept\u00e9 ce compromis entre le nombre de lancements et la probabilit\u00e9 de rupture. Cela n\u2019est arriv\u00e9 qu'une seule fois en deux ans, donc probablement ce n'\u00e9tait pas pour rien.<\/p>\n<p><strong><em>L'\u00e9criture de la premi\u00e8re version du plugin pour Bitbucket nous a pris deux semaines. <\/em><\/strong><\/p>\n<h3>Nouveaux tests<\/h3>\n<p>\nEntre-temps, notre \u00e9quipe continuait de cro\u00eetre. De nouveaux tests \u00e9taient ajout\u00e9s.<\/p>\n<p>Nous avons pens\u00e9 : pourquoi corriger des erreurs si nous pouvons les pr\u00e9venir ? C'est pourquoi nous avons impl\u00e9ment\u00e9 <strong>une analyse de code statique<\/strong>. Nous avons commenc\u00e9 avec lint, qui fait partie de l'Android SDK. Mais \u00e0 l'\u00e9poque, il ne savait pas du tout travailler avec du code Kotlin, et 75 % de notre application \u00e9tait d\u00e9j\u00e0 \u00e9crite en Kotlin. Donc, lint a \u00e9t\u00e9 compl\u00e9t\u00e9 par des <strong>v\u00e9rifications int\u00e9gr\u00e9es d'Android Studio.<\/strong><\/p>\n<p>Pour cela, nous avons d\u00fb beaucoup nous creuser la t\u00eate : prendre Android Studio, l'emballer dans un Docker et l'ex\u00e9cuter sur CI avec un moniteur virtuel, afin qu'elle pense qu'elle \u00e9tait lanc\u00e9e sur un v\u00e9ritable ordinateur portable. Mais cela fonctionnait.<\/p>\n<p>En m\u00eame temps, nous avons commenc\u00e9 \u00e0 \u00e9crire beaucoup de <strong>tests d'instrumentation<\/strong> et avons impl\u00e9ment\u00e9 <strong>des tests de capture d'\u00e9cran<\/strong>C'est le moment o\u00f9 une capture d'\u00e9cran de r\u00e9f\u00e9rence est g\u00e9n\u00e9r\u00e9e pour une petite vue distincte, et le test consiste \u00e0 prendre une capture d'\u00e9cran de cette vue et \u00e0 la comparer pixel par pixel avec la r\u00e9f\u00e9rence. S'il y a une divergence, cela signifie que le design a \u00e9t\u00e9 perturb\u00e9 quelque part ou que quelque chose ne va pas dans les styles.<\/p>\n<p>Mais les tests d'instrumentation et les tests de capture d'\u00e9cran doivent \u00eatre ex\u00e9cut\u00e9s sur des appareils : sur des \u00e9mulateurs ou sur des appareils r\u00e9els. \u00c9tant donn\u00e9 qu'il y a beaucoup de tests et qu'ils sont souvent ex\u00e9cut\u00e9s, il faut toute une ferme. Cr\u00e9er sa propre ferme est trop co\u00fbteux en termes de ressources, c'est pourquoi nous avons trouv\u00e9 une solution pr\u00eate \u00e0 l'emploi : Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\nA \u00e9t\u00e9 choisi parce que Firebase est un produit de Google, donc il doit \u00eatre fiable et il est peu probable qu'il disparaisse un jour. Les prix sont abordables : 5 $ par heure d'utilisation d'un appareil r\u00e9el, 1 $ par heure pour un \u00e9mulateur.<\/p>\n<p><strong><em>L'impl\u00e9mentation de Firebase Test Lab dans notre CI a pris environ trois semaines.<\/em><\/strong><\/p>\n<p>Mais l'\u00e9quipe continuait de grandir, et Firebase a malheureusement commenc\u00e9 \u00e0 nous poser probl\u00e8me. \u00c0 ce moment-l\u00e0, il n'y avait aucun SLA. Parfois, Firebase nous faisait attendre jusqu'\u00e0 ce que le nombre requis d'appareils pour les tests soit disponible, au lieu de commencer imm\u00e9diatement, comme nous le souhaitions. L'attente dans la file pouvait prendre jusqu'\u00e0 une demi-heure, ce qui est tr\u00e8s long. Les tests d'instrumentation \u00e9taient ex\u00e9cut\u00e9s \u00e0 chaque PR, et ces retards ralentissaient vraiment le d\u00e9veloppement, puis est venu le compte pour le mois avec un montant \u00e9lev\u00e9. En r\u00e9sum\u00e9, il a \u00e9t\u00e9 d\u00e9cid\u00e9 d'abandonner Firebase et de d\u00e9velopper en interne, puisque l'\u00e9quipe avait suffisamment grandi.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nNous avons pris Docker, y avons int\u00e9gr\u00e9 des \u00e9mulateurs, et \u00e9crit un petit programme en Python qui \u00e0 tout moment d\u00e9clenche le nombre n\u00e9cessaire d'\u00e9mulateurs dans la version voulue et les arr\u00eate quand il le faut. Et, bien s\u00fbr, quelques scripts bash \u2014 que serait-on sans eux ?<\/p>\n<p><strong><em>La cr\u00e9ation de notre propre environnement de test a demand\u00e9 cinq semaines.<\/em><\/strong><\/p>\n<p>En cons\u00e9quence, chaque pull request avait une liste de v\u00e9rifications \u00e9tendue et bloquante pour la fusion :<\/p>\n<ul>\n<li>Compilation ARK ;<\/li>\n<li>Tests Junit ;<\/li>\n<li>Lint ;<\/li>\n<li>V\u00e9rifications Android Studio ;<\/li>\n<li>Tests d'instrumentation ;<\/li>\n<li>Tests de capture d'\u00e9cran.<\/li>\n<\/ul>\n<p>\nCela a permis d'\u00e9viter de nombreuses pannes possibles. Techniquement, tout fonctionnait, mais les d\u00e9veloppeurs se plaignaient d'attendre trop longtemps les r\u00e9sultats.<\/p>\n<p>Trop longtemps, c'est combien ? Nous avons extrait des donn\u00e9es de Bitbucket et TeamCity dans le syst\u00e8me d'analyse et nous avons constat\u00e9 que <strong>le temps d'attente moyen est de 45 minutes<\/strong>. Donc, un d\u00e9veloppeur, en ouvrant une pull request, attend en moyenne les r\u00e9sultats des builds pendant 45 minutes. \u00c0 mon avis, c'est beaucoup trop, et il est inacceptable de travailler de cette mani\u00e8re.<\/p>\n<p>Bien s\u00fbr, nous avons d\u00e9cid\u00e9 d'acc\u00e9l\u00e9rer tous nos builds.<\/p>\n<h2>Nous nous r\u00e9organisons<\/h2>\n<p>\nEn voyant que les builds restaient souvent en attente, nous avons d'abord <strong>achet\u00e9 du mat\u00e9riel suppl\u00e9mentaire<\/strong> \u2014 le d\u00e9veloppement extensif est le plus simple. Les builds ne sont plus \u00e0 l'arr\u00eat, mais le temps d'attente a diminu\u00e9 seulement l\u00e9g\u00e8rement, car certaines v\u00e9rifications prenaient encore beaucoup de temps.<\/p>\n<h3>Nous \u00e9liminons les v\u00e9rifications trop longues<\/h3>\n<p>\nNotre int\u00e9gration continue peut attraper ce type d'erreurs et de probl\u00e8mes.<\/p>\n<ul>\n<li><strong>\u00c7a ne compile pas<\/strong>. CI peut d\u00e9tecter une erreur de compilation lorsque, \u00e0 cause de changements conflictuels, quelque chose ne compile pas. Comme je l'ai d\u00e9j\u00e0 mentionn\u00e9, \u00e0 ce moment-l\u00e0, personne ne peut rien compiler, le d\u00e9veloppement s'arr\u00eate, et tout le monde devient nerveux.<\/li>\n<li><strong>Bug de comportement<\/strong>. Par exemple, lorsque l'application compile, mais se plante en appuyant sur un bouton, ou que le bouton ne fonctionne tout simplement pas. C'est probl\u00e9matique, car ce genre de bug peut atteindre l'utilisateur.<\/li>\n<li><strong>Bug de mise en page<\/strong>. Par exemple, le bouton fonctionne, mais a d\u00e9cal\u00e9 de 10 pixels vers la gauche.<\/li>\n<li><strong>Augmentation de la dette technique<\/strong>.<\/li>\n<\/ul>\n<p>\nEn regardant cette liste, nous avons r\u00e9alis\u00e9 que seuls les deux premiers points \u00e9taient critiques. Nous voulons d\u00e9tecter ces probl\u00e8mes en priorit\u00e9. Les bugs de mise en page sont identifi\u00e9s lors de l'\u00e9tape de design-review et peuvent \u00eatre facilement corrig\u00e9s \u00e0 ce moment-l\u00e0. Travailler sur la dette technique demande un processus et une planification distincts, c'est pourquoi nous avons d\u00e9cid\u00e9 de ne pas le v\u00e9rifier sur pull request.<\/p>\n<p>En nous basant sur cette classification, nous avons pass\u00e9 en revue toute la liste de v\u00e9rifications. <strong>Nous avons ray\u00e9 Lint<\/strong> et avons d\u00e9plac\u00e9 son lancement \u00e0 la nuit : juste pour qu'il produise un rapport sur le nombre de probl\u00e8mes dans le projet. Nous avons convenu de travailler s\u00e9par\u00e9ment sur la dette technique, et <strong>nous avons compl\u00e8tement abandonn\u00e9 les v\u00e9rifications d'Android Studio<\/strong>. Android Studio dans Docker pour ex\u00e9cuter les inspections semble int\u00e9ressant, mais cela entra\u00eene beaucoup de d\u00e9sagr\u00e9ments en mati\u00e8re de maintenance. Toute mise \u00e0 jour des versions d'Android Studio est une lutte contre des bugs inexplicables. Il \u00e9tait tout aussi difficile de maintenir des tests de capture d'\u00e9cran, car la biblioth\u00e8que n'\u00e9tait pas tr\u00e8s stable, avec des faux positifs. <strong>Nous avons retir\u00e9 les tests de capture d'\u00e9cran de la liste de v\u00e9rifications<\/strong>.<\/p>\n<p>Finalement, il nous reste :<\/p>\n<ul>\n<li>Compilation ARK ;<\/li>\n<li>Tests Junit ;<\/li>\n<li>Des tests d'instrumentation.<\/li>\n<\/ul>\n<h3>Cache Gradle distant<\/h3>\n<p>\nSans lourdes v\u00e9rifications, tout est devenu meilleur. Mais il n'y a pas de limite \u00e0 la perfection !<\/p>\n<p>Notre application \u00e9tait d\u00e9j\u00e0 divis\u00e9e en environ 150 modules gradle. En g\u00e9n\u00e9ral, dans ce cas, le cache Gradle distant fonctionne bien, et nous avons d\u00e9cid\u00e9 de l'essayer.<\/p>\n<p>Le cache distant Gradle est un service qui peut mettre en cache des artefacts de construction pour des t\u00e2ches sp\u00e9cifiques dans des modules distincts. Gradle, au lieu de compiler r\u00e9ellement le code, interroge via HTTP le cache distant et demande si quelqu'un a d\u00e9j\u00e0 ex\u00e9cut\u00e9 cette t\u00e2che. Si oui, il t\u00e9l\u00e9charge simplement le r\u00e9sultat.<\/p>\n<p><strong><em>Lancer le cache distant Gradle est facile, car Gradle fournit une image Docker. Nous avons r\u00e9ussi \u00e0 le faire en trois heures.<\/em><\/strong><\/p>\n<p>Il suffit de lancer Docker et d'ajouter une ligne dans le projet. Mais bien que cela puisse \u00eatre fait rapidement, pour que tout fonctionne bien, cela demande beaucoup de temps.<\/p>\n<p>Ci-dessous, le graphique des \u00e9checs de cache.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAu tout d\u00e9but, le pourcentage de rat\u00e9s de cache \u00e9tait d'environ 65. Apr\u00e8s trois semaines, nous avons r\u00e9ussi \u00e0 le r\u00e9duire \u00e0 20%. Il s'est av\u00e9r\u00e9 que les t\u00e2ches que l'application Android cr\u00e9e ont des d\u00e9pendances transitives \u00e9tranges, ce qui faisait \u00e9chouer Gradle.<\/p>\n<p>En activant le cache, nous avons consid\u00e9rablement acc\u00e9l\u00e9r\u00e9 la construction. Mais en plus de la construction, des tests d'instrumentation sont \u00e9galement ex\u00e9cut\u00e9s, et ils prennent du temps. Il est possible que tous les tests ne doivent pas \u00eatre ex\u00e9cut\u00e9s pour chaque pull request. Pour le d\u00e9couvrir, nous utilisons l'analyse d'impact.<\/p>\n<h3>Analyse d'impact<\/h3>\n<p>\nPour chaque pull request, nous rassemblons le git diff et trouvons les modules Gradle modifi\u00e9s.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIl est judicieux d'ex\u00e9cuter uniquement les tests d'instrumentation qui v\u00e9rifient les modules modifi\u00e9s et tous les modules qui en d\u00e9pendent. Il n'est pas utile d'ex\u00e9cuter des tests pour les modules voisins : le code n'a pas chang\u00e9 et rien ne peut \u00eatre cass\u00e9.<\/p>\n<p>Avec les tests d'instrumentation, ce n'est pas si simple, car ils doivent se trouver dans le module sup\u00e9rieur Application. Nous avons appliqu\u00e9 une heuristique d'analyse de bytecode pour comprendre \u00e0 quel module chaque test appartient.<\/p>\n<p><strong><em>La modernisation des tests d'instrumentation pour qu'ils v\u00e9rifient uniquement les modules impliqu\u00e9s a pris environ huit semaines.<\/em><\/strong><\/p>\n<p>Les mesures pour acc\u00e9l\u00e9rer les v\u00e9rifications ont fonctionn\u00e9 avec succ\u00e8s. Nous avons r\u00e9duit la dur\u00e9e de 45 minutes \u00e0 environ 15 minutes. Un quart d'heure d'attente pour un build est d\u00e9sormais acceptable.<\/p>\n<p>Mais maintenant, les d\u00e9veloppeurs commencent \u00e0 se plaindre qu'ils ne comprennent pas quels builds sont lanc\u00e9s, o\u00f9 voir les logs, pourquoi le build est rouge, quel test a \u00e9chou\u00e9, etc.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLes probl\u00e8mes de r\u00e9troaction ralentissent le d\u00e9veloppement, c'est pourquoi nous avons essay\u00e9 de fournir des informations aussi claires et d\u00e9taill\u00e9es que possible sur chaque PR et build. Nous avons commenc\u00e9 par des commentaires dans Bitbucket sur les PR indiquant quel build a \u00e9chou\u00e9 et pourquoi, et avons envoy\u00e9 des messages cibl\u00e9s dans Slack. Par la suite, nous avons cr\u00e9\u00e9 un tableau de bord pour la page PR avec la liste de tous les builds actuellement en cours d'ex\u00e9cution et leur \u00e9tat : en attente, en cours, \u00e9chou\u00e9 ou termin\u00e9. On peut cliquer sur un build pour acc\u00e9der \u00e0 son log.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>Six semaines ont \u00e9t\u00e9 consacr\u00e9es \u00e0 une r\u00e9troaction d\u00e9taill\u00e9e.<\/em><\/strong><\/p>\n<h2>Plans<\/h2>\n<p>\nPassons \u00e0 l'histoire la plus r\u00e9cente. En r\u00e9solvant la question de la r\u00e9troaction, nous avons atteint un nouveau niveau \u2014 nous avons d\u00e9cid\u00e9 de construire notre ferme d'\u00e9mulateurs. Lorsqu'il y a beaucoup de tests et d'\u00e9mulateurs, il devient difficile de les g\u00e9rer. Au final, tous nos \u00e9mulateurs ont d\u00e9m\u00e9nag\u00e9 dans un cluster k8s avec une gestion dynamique des ressources.<\/p>\n<p>De plus, il y a d'autres projets en cours.<\/p>\n<ul>\n<li><strong>Renvoyer Lint<\/strong> (et d'autres analyses statiques). Nous travaillons d\u00e9j\u00e0 dans ce sens.<\/li>\n<li>Ex\u00e9cuter tous les tests end-to-end <strong>sur toutes les versions du SDK avec l'option de blocage sur PR.<\/strong> Ainsi, nous avons retrac\u00e9 l'\u00e9volution de l'int\u00e9gration continue chez Avito. Maintenant, je voudrais donner quelques conseils du point de vue d'un expert.<\/li>\n<\/ul>\n<p>\nConseils<\/p>\n<h1>Si je ne pouvais donner qu'un seul conseil, ce serait celui-ci :<\/h1>\n<p>\nVeuillez faire attention aux scripts shell !<\/p>\n<blockquote><p>Bash est un outil tr\u00e8s flexible et puissant, il est tr\u00e8s facile et rapide d'\u00e9crire des scripts avec. Mais on peut tomber dans le pi\u00e8ge, et nous y sommes malheureusement tomb\u00e9s.<\/p><\/blockquote>\n<p>\nTout a commenc\u00e9 par des scripts simples qui \u00e9taient ex\u00e9cut\u00e9s sur nos machines de build :<\/p>\n<p>Mais, comme on le sait, tout \u00e9volue et se complique avec le temps \u2014 lan\u00e7ons un script depuis un autre, transmettions-y certains param\u00e8tres \u2014 au final, il a fallu \u00e9crire une fonction qui d\u00e9termine \u00e0 quel niveau de profondeur bash nous sommes pour mettre les bonnes guillemets afin que tout cela fonctionne.<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nVous pouvez imaginer la charge de travail n\u00e9cessaire pour d\u00e9velopper de tels scripts. Je vous conseille de ne pas tomber dans ce pi\u00e8ge.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPar quoi peut-on remplacer ?<\/p>\n<p>Tout langage de script. \u00c9crire en<\/p>\n<ul>\n<li>Python ou Kotlin Script <strong>est plus pratique, car c'est de la programmation, pas du scripting.<\/strong> Ou d\u00e9crire toute la logique des builds sous forme de<\/li>\n<li>t\u00e2ches gradle personnalis\u00e9es <strong>pour votre projet.<\/strong> Nous avons d\u00e9cid\u00e9 de choisir la deuxi\u00e8me option et supprimons progressivement tous les scripts bash en \u00e9crivant de nombreuses t\u00e2ches gradle personnalis\u00e9es.<\/li>\n<\/ul>\n<p>\nConseil n\u00b02 : garder l'infrastructure sous forme de code.<\/p>\n<p><strong>Conseil n\u00b0 2 : conserver l'infrastructure dans le code.<\/strong><\/p>\n<p>C'est pratique lorsque la configuration de l'int\u00e9gration continue n'est pas stock\u00e9e dans l'interface utilisateur de Jenkins ou TeamCity, etc., mais sous forme de fichiers texte directement dans le d\u00e9p\u00f4t du projet. Cela permet de le versionner. Il ne sera pas difficile de revenir en arri\u00e8re ou de construire le code sur une autre branche.<\/p>\n<p>Les scripts peuvent \u00eatre stock\u00e9s dans le projet. Mais que faire avec l'environnement ?<\/p>\n<p><strong>Conseil n\u00b03 : Docker peut aider avec l'environnement.<\/strong><\/p>\n<p>Il aidera certainement les d\u00e9veloppeurs Android, malheureusement pas encore pour iOS.<\/p>\n<p>Voici un exemple de fichier docker simple qui contient JDK et Android SDK :<\/p>\n<pre><code class=\"plaintext\">FROM openjdk:8\n\nENV SDK_URL=\"https:\/\/dl.google.com\/android\/repository\/sdk-tools-linux-3859397.zip\" \n    ANDROID_HOME=\"\/usr\/local\/android-sdk\" \n    ANDROID_VERSION=26 \n    ANDROID_BUILD_TOOLS_VERSION=26.0.2\n\n# T\u00e9l\u00e9charger Android SDK\nRUN mkdir \"$ANDROID_HOME\" .android \n    &amp;&amp; cd \"$ANDROID_HOME\" \n    &amp;&amp; curl -o sdk.zip $SDK_URL \n    &amp;&amp; unzip sdk.zip \n    &amp;&amp; rm sdk.zip \n    &amp;&amp; yes | $ANDROID_HOME\/tools\/bin\/sdkmanager --licenses\n\n# Installer Android Build Tool et biblioth\u00e8ques\nRUN $ANDROID_HOME\/tools\/bin\/sdkmanager --update\nRUN $ANDROID_HOME\/tools\/bin\/sdkmanager \"build-tools;${ANDROID_BUILD_TOOLS_VERSION}\" \n    \"platforms;android-${ANDROID_VERSION}\" \n    \"platform-tools\"\n\nRUN mkdir \/application\nWORKDIR \/application\n<\/code><\/pre>\n<p>\nApr\u00e8s avoir \u00e9crit ce fichier docker (je vous le dis en secret, vous n'avez pas besoin de l'\u00e9crire, vous pouvez en t\u00e9l\u00e9charger un pr\u00eat sur GitHub) et en construisant l'image, vous obtenez une machine virtuelle sur laquelle vous pouvez compiler l'application et ex\u00e9cuter des tests Junit.<\/p>\n<p>Deux principaux arguments pour lesquels cela a du sens : scalabilit\u00e9 et reproductibilit\u00e9. Avec Docker, vous pouvez rapidement cr\u00e9er une dizaine d'agents de build, qui auront exactement le m\u00eame environnement que le pr\u00e9c\u00e9dent. Cela facilite grandement la vie des ing\u00e9nieurs CI. Inclure android-sdk dans Docker est assez simple, avec les \u00e9mulateurs c'est un peu plus compliqu\u00e9 : il faut faire un petit effort (ou encore t\u00e9l\u00e9charger un pr\u00eat sur GitHub).<\/p>\n<p><strong>Conseil n\u00b04 : n'oubliez pas que les v\u00e9rifications ne sont pas faites pour le plaisir, mais pour les gens.<\/strong><\/p>\n<p>Il est tr\u00e8s important pour les d\u00e9veloppeurs d'avoir un retour rapide et, surtout, compr\u00e9hensible : ce qui s'est mal pass\u00e9, quel test a \u00e9chou\u00e9, o\u00f9 consulter le build log.<\/p>\n<p><strong>Conseil n\u00b05 : soyez pragmatiques dans le d\u00e9veloppement de l'int\u00e9gration continue.<\/strong><\/p>\n<p>Comprenez clairement quels types d'erreurs vous souhaitez pr\u00e9venir, combien de ressources et de temps vous \u00eates pr\u00eats \u00e0 investir. Les v\u00e9rifications trop longues peuvent par exemple \u00eatre d\u00e9plac\u00e9es la nuit. Et abandonnez celles qui d\u00e9tectent des erreurs peu importantes.<\/p>\n<p><strong>Conseil n\u00b06 : utilisez des outils pr\u00eats \u00e0 l'emploi.<\/strong><\/p>\n<p>Il existe maintenant de nombreuses entreprises qui proposent une CI cloud.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nC'est une bonne solution pour les petites \u00e9quipes. Pas besoin de g\u00e9rer quoi que ce soit, il suffit de payer un peu d'argent, de rassembler votre application et m\u00eame de lancer des tests d'instrumentation.<\/p>\n<p><strong>Conseil n\u00b07 : dans une grande \u00e9quipe, les solutions in-house sont plus avantageuses.<\/strong><\/p>\n<p>Mais t\u00f4t ou tard, avec la croissance de l'\u00e9quipe, les solutions in-house deviendront plus rentables. Il y a un point \u00e0 consid\u00e9rer avec ces solutions. En \u00e9conomie, il y a la loi des rendements d\u00e9croissants : dans tout projet, chaque am\u00e9lioration suppl\u00e9mentaire devient de plus en plus difficile et n\u00e9cessite de plus en plus d'investissements.<\/p>\n<p>L'\u00e9conomie d\u00e9crit toute notre vie, y compris l'int\u00e9gration continue. J'ai construit un graphique des efforts requis \u00e0 chaque \u00e9tape du d\u00e9veloppement de notre int\u00e9gration continue.<\/p>\n<p><img decoding=\"async\" alt=\"\u00c9volution du CI dans l&#039;\u00e9quipe de d\u00e9veloppement mobile.\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOn peut constater que toute am\u00e9lioration devient de plus en plus difficile. En regardant ce graphique, on peut comprendre qu'il est n\u00e9cessaire de d\u00e9velopper l'int\u00e9gration continue en coh\u00e9rence avec la croissance de la taille de l'\u00e9quipe. Pour une \u00e9quipe de deux personnes, passer 50 jours \u00e0 d\u00e9velopper une ferme interne d'\u00e9mulateurs n'est pas une bonne id\u00e9e. Mais en m\u00eame temps, pour une grande \u00e9quipe, ne pas s'occuper de l'int\u00e9gration continue est \u00e9galement une mauvaise id\u00e9e, car cela n\u00e9cessitera encore plus de temps pour r\u00e9soudre les probl\u00e8mes d'int\u00e9gration, r\u00e9parer la communication, etc.<\/p>\n<p>Nous avons commenc\u00e9 par dire que l'automatisation est n\u00e9cessaire, car les gens sont chers, ils font des erreurs et sont paresseux. Mais l'automatisation est \u00e9galement r\u00e9alis\u00e9e par des gens. Par cons\u00e9quent, ces m\u00eames probl\u00e8mes s'appliquent aussi \u00e0 l'automatisation.<\/p>\n<ul>\n<li>Automatiser co\u00fbte cher. Rappelez-vous du graphique des efforts.<\/li>\n<li>Lors de l'automatisation, les gens se trompent.<\/li>\n<li>Il est parfois tr\u00e8s tentant de ne pas automatiser, car tout fonctionne d\u00e9j\u00e0. Pourquoi am\u00e9liorer encore quelque chose, pourquoi toute cette int\u00e9gration continue ?<\/li>\n<\/ul>\n<p>\nMais j'ai des statistiques : 20 % des builds pr\u00e9sentent des erreurs. Et cela ne se produit pas parce que nos d\u00e9veloppeurs \u00e9crivent mal le code. C'est parce que les d\u00e9veloppeurs sont convaincus que si jamais ils commettent une erreur, elle ne parviendra pas \u00e0 develop, elle sera d\u00e9tect\u00e9e par des contr\u00f4les automatis\u00e9s. Par cons\u00e9quent, les d\u00e9veloppeurs peuvent consacrer plus de temps \u00e0 \u00e9crire du code et \u00e0 faire des choses int\u00e9ressantes, plut\u00f4t qu'\u00e0 tester localement.<\/p>\n<p><strong>Pratiquez l'int\u00e9gration continue. Mais avec mod\u00e9ration.<\/strong><\/p>\n<blockquote><p>Au fait, Nikolai Nesterov ne fait pas seulement de superbes pr\u00e9sentations lui-m\u00eame, mais il fait \u00e9galement partie du comit\u00e9 de programme <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> et aide les autres \u00e0 pr\u00e9parer des pr\u00e9sentations enrichissantes pour vous. Vous pouvez \u00e9valuer la richesse et l'utilit\u00e9 du programme de la prochaine conf\u00e9rence en fonction des th\u00e8mes de <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">l'horaire<\/a><\/noindex>. Pour plus de d\u00e9tails, rendez-vous les 22 et 23 avril \u00e0 l'Infosph\u00e8re.<\/p><\/blockquote>\n<p>Source : <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/447608\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445. \u0423\u0441\u043b\u043e\u0432\u0438\u044f \u0443\u0441\u043f\u0435\u0445\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0445\u0435\u043c\u044b. \u041d\u0430\u043f\u0438\u0441\u0430\u0432 \u043a\u043e\u0434, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d: \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0432\u0430\u0448\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0438. \u0415\u0441\u043b\u0438 \u043e\u0431\u0430 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f, \u0442\u043e \u0432\u044b \u043d\u0430 \u043f\u0443\u0442\u0438 \u043a \u0443\u0441\u043f\u0435\u0445\u0443. \u0427\u0442\u043e\u0431\u044b \u043b\u0435\u0433\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u044d\u0442\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0438 \u043d\u0435 \u0441\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0441 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":23270,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-31304","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"fr_FR\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u042d\u0432\u043e\u043b\u044e\u0446\u0438\u044f CI \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:40:34+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:40:34+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47 L'\u00e9volution du CI dans l'\u00e9quipe de d\u00e9veloppement mobile | ProHoster","description":"Aujourd'hui, la plupart des produits logiciels sont d\u00e9velopp\u00e9s en \u00e9quipes.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u042d\u0432\u043e\u043b\u044e\u0446\u0438\u044f CI \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 | ProHoster","og:description":"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.","og:url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:40:34+00:00","article:modified_time":"2019-10-31T18:40:34+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"31304","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 05:30:21","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 03:19:49","updated":"2026-01-21 05:30:21","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/31304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=31304"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/31304\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/23270"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=31304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=31304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=31304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}