{"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\/it\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","title":{"rendered":"L'evoluzione CI nel team di sviluppo mobile","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Oggi la maggior parte dei prodotti software viene sviluppata in team. Le condizioni per il successo dello sviluppo in team possono essere rappresentate come uno schema semplice.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDopo aver scritto il codice, devi assicurarti che esso:<\/p>\n<ol>\n<li>Funzioni.<\/li>\n<li>Non rompa nulla, compreso il codice scritto dai tuoi colleghi.<\/li>\n<\/ol>\n<p>\nSe entrambe le condizioni sono soddisfatte, sei sulla strada del successo. Per controllare facilmente queste condizioni e non deviare da un percorso vantaggioso, \u00e8 stata ideata l'Integrazione Continua.<\/p>\n<p>La CI \u00e8 un flusso di lavoro in cui integri il tuo codice nel codice comune del prodotto il pi\u00f9 frequentemente possibile. E non solo integri, ma verifichi anche costantemente che tutto funzioni. Poich\u00e9 le verifiche devono essere molte e frequenti, \u00e8 il momento di considerare l'automazione. Puoi controllare tutto manualmente, ma non dovresti, ecco perch\u00e9.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Le persone costano care<\/strong>. L'ora di lavoro di qualsiasi programmatore costa pi\u00f9 dell'ora di lavoro di qualsiasi server.<\/li>\n<li><strong>Le persone commettono errori<\/strong>. Pertanto, possono sorgere situazioni in cui i test vengono eseguiti su un ramo sbagliato o viene raccolto un commit errato per i tester.<\/li>\n<li><strong>Le persone sono pigre<\/strong>. Di tanto in tanto, quando concludo un compito, mi viene in mente: \u00abMa che cosa c'\u00e8 da controllare? Ho scritto due righe \u2014 sicuramente funziona tutto!\u00bb Credo che anche alcuni di voi abbiano questi pensieri di tanto in tanto. Ma bisogna sempre controllare.<\/li>\n<\/ul>\n<p>\nCome abbiamo implementato e sviluppato il Continuous Integration nel team di sviluppo mobile di Avito, come siamo passati da 0 a 450 compilazioni al giorno, e cosa fanno le macchine per la build accumulando 200 ore al giorno, ce lo racconta Nikolay Nesterov (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/nnesterov\/\" class=\"user_link\">nnesterov<\/a><\/noindex>) \u2014 partecipante a tutti i cambiamenti evolutivi del CI\/CD dell'app Android.<\/p>\n<p>Il racconto si basa sull'esempio del team Android, ma la maggior parte degli approcci \u00e8 applicabile anche su 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=\"Riproduci video\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\nUn tempo, nel team Android di Avito, lavorava una sola persona. Di per s\u00e9, non aveva bisogno di nulla del Continuous Integration: non c'era nessuno con cui integrarsi.<\/p>\n<p>Ma l'app cresceva, comparivano sempre pi\u00f9 nuove attivit\u00e0 e, di conseguenza, il team cresceva. A un certo punto, \u00e8 arrivato il momento di organizzare in modo pi\u00f9 formale il processo di integrazione del codice. Si \u00e8 deciso di utilizzare Git flow.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/f433effc5ab5a59de3d6bd20a7a87057.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIl concetto di Git flow \u00e8 noto: nel progetto c'\u00e8 un ramo comune chiamato develop, e per ogni nuova funzionalit\u00e0 gli sviluppatori creano un ramo separato, ci fanno dei commit, lo spingono, e quando desiderano unire il loro codice nel ramo develop, aprono una pull request. Per condividere conoscenze e discutere approcci, abbiamo introdotto il code review, ovvero i colleghi devono esaminare e confermare il codice l'uno dell'altro.<\/p>\n<h2>Verifiche<\/h2>\n<p>\nEsaminare il codice visivamente \u00e8 utile, ma non sufficiente. Pertanto, vengono introdotte verifiche automatiche.<\/p>\n<ul>\n<li>Per prima cosa verifichiamo <strong>la build ARK<\/strong>.<\/li>\n<li>Molti <strong>test Junit<\/strong>.<\/li>\n<li><strong>Contiamo la code coverage<\/strong>, visto che stiamo eseguendo i test.<\/li>\n<\/ul>\n<p>\nPer capire come dovremmo eseguire queste verifiche, guardiamo al processo di sviluppo in Avito.<\/p>\n<p>Schematicamente, pu\u00f2 essere rappresentato cos\u00ec:<\/p>\n<ul>\n<li>Lo sviluppatore scrive codice sul proprio laptop. Pu\u00f2 eseguire le verifiche di integrazione proprio qui \u2014 oppure tramite hook di commit o semplicemente eseguendo le verifiche in background.<\/li>\n<li>Dopo che lo sviluppatore ha spinto il codice, apre una pull request. Affinch\u00e9 il suo codice venga incluso nel ramo develop, deve superare il code review e ottenere un numero sufficiente di approvazioni. Le verifiche e le build possono essere attivate qui: finch\u00e9 tutte le build non sono riuscite, non si pu\u00f2 unire la pull request.<\/li>\n<li>Dopo che la pull request \u00e8 stata unita e il codice \u00e8 passato a develop, si pu\u00f2 scegliere un momento conveniente: ad esempio, di notte, quando i server sono liberi, e lanciare quanti pi\u00f9 controlli possibile.<\/li>\n<\/ul>\n<p>\nA nessuno piace lanciare i controlli sul proprio laptop. Quando lo sviluppatore ha finito la funzione, vuole caricarla il prima possibile e aprire la pull request. Se in quel momento vengono avviati dei controlli lunghi, non solo \u00e8 sgradevole, ma rallenta lo sviluppo: mentre il laptop effettua il controllo, non \u00e8 possibile lavorare normalmente.<\/p>\n<p>Ci \u00e8 piaciuto molto lanciare i controlli di notte, perch\u00e9 ci sono tempi e server a disposizione, si pu\u00f2 fare un bel lavoro. Purtroppo, quando il codice della funzione \u00e8 passato a develop, lo sviluppatore ha gi\u00e0 molta meno motivazione per correggere gli errori trovati dal CI. A volte mi sono ritrovato a pensare, mentre guardavo il report mattutino con tutti gli errori trovati, che li avrei sistemati pi\u00f9 tardi, perch\u00e9 ora c'\u00e8 un compito interessante in Jira che voglio iniziare a fare.<\/p>\n<p>Se i controlli bloccano la pull request, la motivazione \u00e8 sufficiente, perch\u00e9 finch\u00e9 i build non diventano verdi, il codice non passer\u00e0 a develop, e quindi il compito non sar\u00e0 completato.<\/p>\n<p>Alla fine abbiamo scelto una strategia del genere: durante la notte eseguiamo il massimo possibile di controlli, mentre i pi\u00f9 critici e, soprattutto i pi\u00f9 veloci, li lanciamo al momento della pull request. Ma non ci fermiamo qui: stiamo parallelamente ottimizzando la velocit\u00e0 dei controlli per poterli trasferire dal regime notturno ai controlli sulle pull request.<\/p>\n<p>A quel tempo, tutte le nostre build passavano abbastanza velocemente, quindi abbiamo semplicemente attivato i bloccanti per la pull request, includendo la build ARC, i test Junit e il calcolo della copertura del codice. Abbiamo attivato, riflettuto e poi rinunciato alla copertura del codice, poich\u00e9 abbiamo ritenuto che non fosse necessaria.<\/p>\n<p><strong><em>Ci sono voluti due giorni per configurare il CI di base (qui e di seguito, la stima temporale \u00e8 approssimativa e serve a dare un'idea della scala). <\/em><\/strong><\/p>\n<p>Dopo ci\u00f2, abbiamo cominciato a chiederci: stiamo controllando correttamente? Stiamo lanciando le build sulla pull request in modo corretto?<\/p>\n<p>Eseguivamo la build sull'ultimo commit del ramo da cui era aperta la pull request. Ma i controlli di quel commit possono mostrare solo che il codice scritto dallo sviluppatore funziona. Non dimostrano che nulla sia stato rotto. In realt\u00e0, \u00e8 necessario controllare lo stato del ramo develop dopo che la funzionalit\u00e0 \u00e8 stata integrata in esso.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPer questo abbiamo scritto un semplice 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>\nQui si recuperano semplicemente tutte le modifiche pi\u00f9 recenti da develop e si fondono nel ramo attuale. Abbiamo aggiunto lo script premerge.sh come primo passo di tutte le build e abbiamo iniziato a controllare esattamente ci\u00f2 che vogliamo, ovvero <strong>integrazione<\/strong>.<\/p>\n<p><strong><em>Ci sono voluti tre giorni per localizzare i problemi, cercare soluzioni e scrivere questo script.<\/em><\/strong><\/p>\n<p>L'applicazione si \u00e8 evoluta, le attivit\u00e0 aumentavano, il team cresceva e premerge.sh ha iniziato a darci problemi. Modifiche conflittuali infiltravano develop e rompevano la build.<\/p>\n<p>Un esempio di come ci\u00f2 accade:<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDue sviluppatori iniziano contemporaneamente a lavorare sulle funzionalit\u00e0 A e B. Lo sviluppatore della funzionalit\u00e0 A scopre una funzione non utilizzata nel progetto <code>answer()<\/code> e, da buon scout, la rimuove. Nel frattempo, lo sviluppatore della funzionalit\u00e0 B aggiunge una nuova chiamata a questa funzione nel proprio ramo.<\/p>\n<p>Gli sviluppatori completano il lavoro e aprono entrambi un pull request allo stesso tempo. Vengono avviate le build, premerge.sh controlla entrambi i pull request rispetto allo stato attuale di develop \u2014 tutti i controlli sono verdi. Dopodich\u00e9 viene fuso il pull request della funzionalit\u00e0 A, viene fuso il pull request della funzionalit\u00e0 B\u2026 Boom! Develop si rompe, perch\u00e9 nel codice di develop c'\u00e8 una chiamata a una funzione inesistente.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuando non si riesce a sviluppare, \u00e8 <strong>una catastrofe locale<\/strong>. L'intero team non riesce a compilare e a consegnare nulla per i test.<\/p>\n<p>\u00c8 successo che mi sono occupato pi\u00f9 frequentemente di compiti infrastrutturali: analisi, rete, basi di dati. Cio\u00e8, proprio io ho scritto quelle funzioni e classi che usano gli altri sviluppatori. Per questo motivo mi sono spesso trovato in situazioni simili. A un certo punto avevo anche un'immagine simile a questa.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPoich\u00e9 questo non ci soddisfaceva, abbiamo iniziato a lavorare su opzioni per prevenire ci\u00f2.<\/p>\n<h2>Come non rompere develop<\/h2>\n<p>\nPrima opzione: <strong>ricompilare tutte le pull request quando si aggiorna develop. <\/strong>Se nel nostro esempio la pull request della funzionalit\u00e0 A arriva per prima in develop, la pull request della funzionalit\u00e0 B deve essere ricompilata, e, di conseguenza, non superer\u00e0 i controlli a causa di un errore di compilazione.<\/p>\n<p>Per capire quanto tempo ci vorr\u00e0, consideriamo un esempio con due PR. Apriamo due PR: due build, due avvii di controlli. Dopo che il primo PR \u00e8 stato integrato in develop, il secondo deve essere ricompilato. In totale, per due PR occorrono tre avvii di controlli: 2 + 1 = 3.<\/p>\n<p>Fondamentalmente va bene. Ma abbiamo esaminato le statistiche, e la situazione tipica nel nostro team prevedeva 10 PR aperti; la somma dei controlli sarebbe una progressione: 10 + 9 + ... + 1 = 55. Quindi, per approvare 10 PR, bisogna compilare 55 volte. E questo in una situazione ideale, dove tutti i controlli passano al primo tentativo, e nessuno apre una pull request aggiuntiva mentre si elaborano queste dieci.<\/p>\n<p>Immagina di essere uno sviluppatore che deve premere il pulsante \"merge\" per primo, perch\u00e9 se lo fa il tuo vicino, dovrai aspettare che tutte le compilazioni vengano eseguite da capo... No, cos\u00ec non va, rallenter\u00e0 seriamente lo sviluppo.<\/p>\n<p>Un secondo possibile modo: <strong>compilare la pull request dopo la revisione del codice. <\/strong>In altre parole, aprite una pull request, ottenete il numero necessario di approvazioni dai colleghi, apportate le correzioni necessarie e poi avviate le build. Se hanno esito positivo, la pull request viene unita a develop. In questo caso non ci sono ulteriori riavvii, ma il feedback \u00e8 notevolmente rallentato. Io, come sviluppatore, voglio vedere immediatamente se la pull request si costruisce. Ad esempio, se un test fallisce, deve essere corretto rapidamente. Con un build ritardato, il feedback \u00e8 rallentato, e di conseguenza l'intero processo di sviluppo ne risente. Anche a noi non piaceva questo.<\/p>\n<p>Di conseguenza, \u00e8 rimasta solo la terza opzione \u2014 <strong>inventare di nuovo<\/strong>. Tutto il nostro codice, tutti i nostri sorgenti sono memorizzati nel repository sul server Bitbucket. Pertanto, abbiamo dovuto sviluppare un plugin per Bitbucket.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuesto plugin sovrascrive il meccanismo di fusione delle pull request. Il processo inizia normalmente: si apre una PR, si avviano tutte le build e si effettua la revisione del codice. Ma dopo che la revisione del codice \u00e8 stata superata e lo sviluppatore decide di fare clic su \"merge\", il plugin verifica in base a quale stato di develop sono state eseguite le verifiche. Se dopo le build develop \u00e8 stato aggiornato, il plugin non permetter\u00e0 di fondere quella pull request nel ramo principale. Riavvier\u00e0 semplicemente le build in base al nuovo develop.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNel nostro esempio con le modifiche in conflitto, tali build non passeranno a causa di un errore di compilazione. Di conseguenza, lo sviluppatore della funzionalit\u00e0 B dovr\u00e0 correggere il codice e riavviare le verifiche, quindi il plugin applicher\u00e0 automaticamente la pull request.<\/p>\n<p>Prima dell'introduzione di questo plugin, avevamo in media 2,7 avvii di verifica per ogni pull request. Con il plugin, sono diventati 3,6. Siamo soddisfatti di questo.<\/p>\n<p>\u00c8 importante notare che questo plugin ha un difetto: riavvia il build solo una volta. Questo significa che rimane una piccola finestra attraverso la quale potrebbero entrare modifiche conflittuali nello sviluppo. Ma la probabilit\u00e0 che ci\u00f2 accada \u00e8 bassa, e quindi abbiamo accettato questo compromesso tra il numero di esecuzioni e la possibilit\u00e0 di problemi. In due anni \u00e8 successo solo una volta, quindi probabilmente non \u00e8 stato vano.<\/p>\n<p><strong><em>La scrittura della prima versione del plugin per Bitbucket ci ha richiesto due settimane. <\/em><\/strong><\/p>\n<h3>Nuove verifiche<\/h3>\n<p>\nNel frattempo, il nostro team ha continuato a crescere. Sono state aggiunte nuove verifiche.<\/p>\n<p>Abbiamo pensato: perch\u00e9 riparare gli errori, se possiamo prevenirli? E per questo abbiamo implementato <strong>senza eseguirlo, sia un controllo durante l'esecuzione (<\/strong>. Siamo partiti da lint, che era incluso nell'Android SDK. Ma all'epoca non era in grado di lavorare con codice Kotlin, e gi\u00e0 il 75% dell'applicazione era scritto in Kotlin. Cos\u00ec sono stati aggiunti a lint dei controlli integrati <strong>di Android Studio.<\/strong><\/p>\n<p>Per farlo abbiamo dovuto inventare strategie complesse: prendere Android Studio, incapsularlo in Docker e farlo girare su CI con un monitor virtuale, per farle credere di essere avviato su un laptop reale. Ma ha funzionato.<\/p>\n<p>In questo periodo abbiamo anche iniziato a scrivere molti <strong>test di strumentazione<\/strong> e abbiamo implementato <strong>il testing con screenshot.<\/strong>. Si tratta di generare uno screenshot di riferimento per una piccola vista separata, e il test consiste nel catturare uno screenshot della vista e confrontarlo pixel per pixel con il riferimento. Se ci sono differenze, significa che la grafica \u00e8 andata in tilt o c'\u00e8 qualcosa di sbagliato negli stili.<\/p>\n<p>Ma i test di strumentazione e i test degli screenshot devono essere eseguiti su dispositivi: su emulatori o su dispositivi reali. Dato che ci sono molti test e vengono eseguiti frequentemente, \u00e8 necessaria un'intera fattoria. Creare la propria fattoria \u00e8 troppo dispendioso in termini di tempo, quindi abbiamo trovato una soluzione pronta: Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\n\u00c8 stato scelto perch\u00e9 Firebase \u00e8 un prodotto Google, quindi dovrebbe essere affidabile e difficilmente smetter\u00e0 di esistere. I prezzi sono accessibili: $5 all'ora per l'uso di un dispositivo reale, $1 all'ora per l'uso di un emulatore.<\/p>\n<p><strong><em>Ci sono volute circa tre settimane per integrare Firebase Test Lab nel nostro CI.<\/em><\/strong><\/p>\n<p>Ma il team continuava a crescere, e purtroppo Firebase ci ha deluso. A quel punto non aveva alcun SLA. A volte Firebase ci faceva aspettare che fosse disponibile il giusto numero di dispositivi per i test, invece di farli partire subito, come desideravamo. L'attesa in coda durava fino a mezz'ora, ed era un tempo molto lungo. I test di strumentazione venivano eseguiti su ogni PR, i ritardi rallentavano molto lo sviluppo, e poi arrivava anche la bolletta del mese con una cifra rotonda. In generale, \u00e8 stato deciso di rinunciare a Firebase e di sviluppare in house, dato che il team era cresciuto abbastanza.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nAbbiamo preso Docker, inserito emulatori al suo interno, scritto un semplice programma in Python che, al momento giusto, avvia il numero necessario di emulatori nella versione desiderata e li ferma quando necessario. E, naturalmente, un paio di script bash \u2014 come potrebbe mancare?<\/p>\n<p><strong><em>Ci sono volate cinque settimane per creare il nostro ambiente di test.<\/em><\/strong><\/p>\n<p>Di conseguenza, ogni pull request aveva una lunga lista di controlli che bloccava la fusione:<\/p>\n<ul>\n<li>Costruzione ARK;<\/li>\n<li>Test Junit;<\/li>\n<li>Lint;<\/li>\n<li>Controlli di Android Studio;<\/li>\n<li>Test di strumentazione;<\/li>\n<li>Test di screenshot.<\/li>\n<\/ul>\n<p>\nQuesto ha prevenuto molti possibili guasti. Tecnicamente tutto funzionava, ma gli sviluppatori si lamentavano che i tempi di attesa per i risultati erano troppo lunghi.<\/p>\n<p>Troppo lungo \u2014 quanto? Abbiamo esportato i dati da Bitbucket e TeamCity nel sistema di analisi e abbiamo capito che <strong>il tempo medio di attesa \u00e8 di 45 minuti<\/strong>. Quindi, uno sviluppatore che apre una pull request attende in media i risultati delle build per 45 minuti. A mio parere, \u00e8 davvero troppo, e non possiamo lavorare in questo modo.<\/p>\n<p>Naturalmente, abbiamo deciso di accelerare tutte le nostre build.<\/p>\n<h2>Acceleriamo<\/h2>\n<p>\nVedendo che le build spesso erano in coda, abbiamo prima di tutto <strong>acquistato ulteriore hardware<\/strong> \u2014 lo sviluppo estensivo \u00e8 la soluzione pi\u00f9 semplice. Le build hanno smesso di rimanere in coda, ma il tempo di attesa \u00e8 diminuito solo di poco, perch\u00e9 alcune verifiche richiedevano comunque molto tempo.<\/p>\n<h3>Eliminiamo i controlli troppo lunghi<\/h3>\n<p>\nIl nostro Continuous Integration poteva catturare tali tipi di errori e problemi.<\/p>\n<ul>\n<li><strong>Non si compila<\/strong>. CI pu\u00f2 intercettare un errore di compilazione quando, a causa di modifiche conflittuali, qualcosa non si compila. Come ho gi\u00e0 detto, in quel momento nessuno pu\u00f2 compilare nulla, lo sviluppo si ferma e tutti sono nervosi.<\/li>\n<li><strong>Un bug nel comportamento<\/strong>. Ad esempio, quando l'app si blocca al clic su un pulsante o il pulsante non risponde affatto. Questo \u00e8 un problema, poich\u00e9 un bug simile pu\u00f2 arrivare all'utente.<\/li>\n<li><strong>Bug nel layout<\/strong>. Ad esempio, il pulsante si pu\u00f2 cliccare, ma \u00e8 spostato di 10 pixel a sinistra.<\/li>\n<li><strong>Aumento del debito tecnico<\/strong>.<\/li>\n<\/ul>\n<p>\nEsaminando questa lista, abbiamo capito che solo i primi due punti sono critici. Vogliamo catturare questi problemi prima di tutto. I bug nel layout vengono individuati durante la fase di design review e possono essere facilmente corretti in quel momento. Lavorare con il debito tecnico richiede un processo e una pianificazione separati, quindi abbiamo deciso di non controllarlo in un pull request.<\/p>\n<p>Sulla base di questa classificazione, abbiamo esaminato l'intero elenco di controlli. <strong>Abbiamo escluso Lint<\/strong> e abbiamo spostato la sua esecuzione alla notte: semplicemente per avere un rapporto su quanti problemi ci sono nel progetto. Ci siamo accordati per lavorare separatamente sul debito tecnico, e <strong>abbiamo completamente abbandonato i controlli di Android Studio<\/strong>. Android Studio in Docker per eseguire ispezioni sembra interessante, ma causa molti problemi di supporto. Qualsiasi aggiornamento delle versioni di Android Studio \u00e8 una lotta contro bug incomprensibili. \u00c8 stato altrettanto difficile mantenere i test degli screenshot, perch\u00e9 la libreria non era molto stabile, si verificavano falsi positivi. <strong>I test degli screenshot sono stati rimossi dalla lista di controlli<\/strong>.<\/p>\n<p>Alla fine ci sono rimasti:<\/p>\n<ul>\n<li>Costruzione ARK;<\/li>\n<li>Test Junit;<\/li>\n<li>Test di strumentazione.<\/li>\n<\/ul>\n<h3>Cache remota di Gradle<\/h3>\n<p>\nSenza controlli pesanti, tutto \u00e8 migliorato. Ma non c'\u00e8 limite al miglioramento!<\/p>\n<p>La nostra applicazione era gi\u00e0 suddivisa in circa 150 moduli gradle. Di solito, in questo caso, la cache remota di Gradle funziona bene, e abbiamo deciso di provarla.<\/p>\n<p>La cache remota di Gradle \u00e8 un servizio che pu\u00f2 memorizzare nella cache gli artefatti di build per compiti specifici in moduli distinti. Gradle, invece di compilare effettivamente il codice, contatta la cache remota via HTTP e chiede se qualcuno ha gi\u00e0 eseguito quel compito. Se s\u00ec, scarica semplicemente il risultato.<\/p>\n<p><strong><em>Eseguire la cache remota di Gradle \u00e8 facile, perch\u00e9 Gradle fornisce un'immagine Docker. Siamo riusciti a farlo in tre ore.<\/em><\/strong><\/p>\n<p>\u00c8 bastato avviare Docker e inserire una riga nel progetto. Anche se avviarlo \u00e8 rapido, far funzionare tutto bene richieder\u00e0 abbastanza tempo.<\/p>\n<p>Di seguito il grafico dei cache misses.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAll'inizio, il tasso di miss del cache era intorno al 65%. Dopo tre settimane, siamo riusciti a ridurre questa cifra al 20%. Si \u00e8 scoperto che i task raccolti dall'applicazione Android hanno strane dipendenze transitiviche, a causa delle quali Gradle falliva nel colpire il cache.<\/p>\n<p>Connettendo il cache, abbiamo notevolmente accelerato la compilazione. Tuttavia, oltre alla compilazione, vengono eseguiti anche i test di strumentazione, che richiedono tempo. Potrebbe non essere necessario eseguire tutti i test per ogni pull request. Per scoprirlo, utilizziamo l'analisi d'impatto.<\/p>\n<h3>Analisi d'impatto<\/h3>\n<p>\nNella pull request raccogliamo il git diff e troviamo i moduli Gradle modificati.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nHa senso eseguire solo i test di strumentazione che verificano i moduli modificati e tutti i moduli che dipendono da essi. Non ha senso eseguire test per moduli vicini: il codice non \u00e8 cambiato e nulla pu\u00f2 rompersi.<\/p>\n<p>I test di instrumentation non sono cos\u00ec semplici, poich\u00e9 devono trovarsi nel modulo di livello pi\u00f9 alto dell'Application. Abbiamo applicato un'euristica con analisi del bytecode per comprendere a quale modulo appartiene ciascun test.<\/p>\n<p><strong><em>La modernizzazione del funzionamento dei test di instrumentation, in modo che verifichino solo i moduli coinvolti, ha richiesto circa otto settimane.<\/em><\/strong><\/p>\n<p>Le misure per accelerare i controlli hanno avuto successo. Siamo passati da 45 minuti a circa 15. Aspettare un quarto d'ora per un build \u00e8 diventato normale.<\/p>\n<p>Ma ora gli sviluppatori hanno iniziato a lamentarsi che non \u00e8 chiaro quali build siano in corso, dove guardare i log, perch\u00e9 il build \u00e8 fallito, quale test ha dato errore, ecc.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI problemi di feedback rallentano lo sviluppo, quindi abbiamo cercato di fornire informazioni chiare e dettagliate su ogni PR e build. Abbiamo iniziato con commenti in Bitbucket ai PR specificando quale build \u00e8 fallita e perch\u00e9, scrivendo messaggi diretti in Slack. Alla fine, abbiamo creato una pagina PR dashboard con un elenco di tutti i build attualmente in esecuzione e il loro stato: in coda, in esecuzione, fallito o completato. \u00c8 possibile cliccare su un build per accedere al suo log.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>Per fornire un feedback dettagliato sono state necessarie sei settimane.<\/em><\/strong><\/p>\n<h2>Piani<\/h2>\n<p>\nPassiamo alla nostra storia pi\u00f9 recente. Risolvendo la questione del feedback, siamo saliti a un nuovo livello: abbiamo deciso di costruire la nostra fattoria di emulatori. Quando ci sono molti test e emulatori, diventa difficile gestirli. Alla fine, tutti i nostri emulatori sono stati spostati in un cluster k8s con gestione flessibile delle risorse.<\/p>\n<p>Inoltre, ci sono altri piani.<\/p>\n<ul>\n<li><strong>Restituire Lint<\/strong> (e altri analizzatori statici). Stiamo gi\u00e0 lavorando in questa direzione.<\/li>\n<li>Eseguire tutti i test end-to-end <strong>su tutte le versioni dell'SDK.<\/strong> Quindi, abbiamo tracciato la storia dello sviluppo della Continuous Integration in Avito. Ora voglio dare alcuni consigli da esperto.<\/li>\n<\/ul>\n<p>\nSe potessi dare solo un consiglio, sarebbe questo:<\/p>\n<h1>Consigli<\/h1>\n<p>\nPer favore, abbiate cura con gli script shell!<\/p>\n<blockquote><p>Bash \u00e8 uno strumento molto flessibile e potente, ed \u00e8 molto comodo e veloce scrivere script. Ma ci si pu\u00f2 incappare in trappole, e purtroppo ci siamo caduti.<\/p><\/blockquote>\n<p>\nTutto \u00e8 iniziato con semplici script che venivano eseguiti sulle nostre macchine di build:<\/p>\n<p>Tutto \u00e8 cominciato con semplici script che venivano eseguiti sulle nostre macchine di build:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nTuttavia, come sappiamo, tutto si sviluppa e si complica nel tempo: eseguiamo uno script da un altro, passiamo alcuni parametri, e alla fine \u00e8 stata scritta una funzione che determina a quale livello di annidamento si troviamo in bash per inserire le virgolette giuste affinch\u00e9 tutto funzioni.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nRiuscite a immaginare quanto tempo richieda lo sviluppo di script del genere? Vi consiglio di non cadere in questa trappola.<\/p>\n<p>Cosa si pu\u00f2 usare al suo posto?<\/p>\n<ul>\n<li>Qualsiasi linguaggio di scripting. Scrivere in <strong>Python o Kotlin Script<\/strong> \u00e8 pi\u00f9 comodo, perch\u00e9 si tratta di programmazione, non di script.<\/li>\n<li>Oppure descrivere tutta la logica dei build sotto forma di <strong>Custom gradle tasks<\/strong> per il vostro progetto.<\/li>\n<\/ul>\n<p>\nAbbiamo deciso di scegliere la seconda opzione e stiamo sistematicamente rimuovendo tutti gli script bash per scrivere molte custom gradle tasks.<\/p>\n<p><strong>Consiglio n. 2: mantenere l'infrastruttura nel codice.<\/strong><\/p>\n<p>\u00c8 comodo quando la configurazione della Continuous Integration non \u00e8 memorizzata nell'interfaccia UI di Jenkins o TeamCity, ma in file di testo direttamente nel repository del progetto. Questo garantisce la versioning. Sar\u00e0 facile tornare indietro o costruire il codice su un'altra branch.<\/p>\n<p>Gli script possono essere memorizzati nel progetto. E per l'ambiente?<\/p>\n<p><strong>Consiglio n. 3: Docker pu\u00f2 aiutare con l'ambiente.<\/strong><\/p>\n<p>Sar\u00e0 sicuramente utile per gli sviluppatori Android, ma purtroppo non per iOS.<\/p>\n<p>Ecco un esempio di un semplice file Docker che contiene JDK e 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# Scarica 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# Installa Android Build Tool e Librerie\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>\nScrivendo questo file Docker (ti confido, non \u00e8 necessario scriverlo, puoi semplicemente scaricare uno pronto da GitHub) e costruendo l'immagine, ottieni una macchina virtuale su cui puoi compilare l'applicazione e lanciare i test JUnit.<\/p>\n<p>Due principali argomenti per cui ha senso: scalabilit\u00e0 e ripetibilit\u00e0. Con Docker \u00e8 possibile avviare rapidamente una dozzina di agenti di build che avranno esattamente lo stesso ambiente di prima. Questo facilita notevolmente il lavoro degli ingegneri CI. Inserire l'android-sdk in Docker \u00e8 molto semplice, con gli emulatori \u00e8 un po' pi\u00f9 complesso: sar\u00e0 necessario impegnarsi un po' (o scaricare di nuovo una versione pronta da GitHub).<\/p>\n<p><strong>Consiglio n. 4: non dimenticare che le verifiche si fanno non per il gusto di farle, ma per le persone.<\/strong><\/p>\n<p>Per gli sviluppatori \u00e8 fondamentale avere un feedback veloce e, soprattutto, chiaro: cosa si \u00e8 rotto, quale test \u00e8 fallito, dove guardare il build log.<\/p>\n<p><strong>Consiglio n. 5: sii pragmatico nello sviluppo dell'integrazione continua.<\/strong><\/p>\n<p>Comprendi chiaramente quali tipi di errori vuoi prevenire, quanto sei disposto a spendere in termini di risorse, tempo ed energia della macchina. Le verifiche troppo lunghe possono essere spostate di notte. E quelle che catturano errori non molto importanti possono essere eliminate del tutto.<\/p>\n<p><strong>Consiglio n. 6: utilizza strumenti pronti all'uso.<\/strong><\/p>\n<p>Ora ci sono molte aziende che offrono CI in cloud.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPer piccoli team, \u00e8 una buona soluzione. Non c'\u00e8 bisogno di mantenere nulla, basta pagare un po' di soldi, assemblare la propria applicazione e persino eseguire i test di strumentazione.<\/p>\n<p><strong>Consiglio n. 7: in un grande team, le soluzioni interne sono pi\u00f9 vantaggiose.<\/strong><\/p>\n<p>Ma prima o poi, con la crescita del team, le soluzioni interne diventeranno pi\u00f9 convenienti. C'\u00e8 un aspetto da considerare riguardo a queste soluzioni: nell'economia, esiste la legge dei rendimenti decrescenti: in ogni progetto, ogni successivo miglioramento diventa sempre pi\u00f9 difficile e richiede investimenti maggiori.<\/p>\n<p>L'economia descrive tutta la nostra vita, incluso il Continuous Integration. Ho costruito un grafico del carico di lavoro per ciascuna fase dello sviluppo del nostro Continuous Integration.<\/p>\n<p><img decoding=\"async\" alt=\"L&#039;evoluzione CI nel team di sviluppo mobile\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n\u00c8 evidente che qualsiasi miglioramento diventa sempre pi\u00f9 difficile da ottenere. Guardando questo grafico, si pu\u00f2 capire che lo sviluppo del Continuous Integration deve avvenire in modo coordinato con la crescita delle dimensioni del team. Per un team di due persone, spendere 50 giorni per sviluppare una fattoria interna di emulatori \u00e8 un'idea poco valida. Ma non dedicarsi affatto al Continuous Integration in un grande team \u00e8 anch'essa una cattiva idea, perch\u00e9 ci vorr\u00e0 ancora pi\u00f9 tempo per risolvere i problemi di integrazione, riparare la comunicazione, ecc.<\/p>\n<p>Abbiamo iniziato con il concetto che l'automazione \u00e8 necessaria, perch\u00e9 le persone costano, commettono errori e sono inclini alla pigrizia. Ma anche l'automazione \u00e8 fatta da persone. Quindi, questi problemi riguardano anche l'automazione.<\/p>\n<ul>\n<li>L'automazione \u00e8 costosa. Ricordate il grafico dei costi lavorativi.<\/li>\n<li>Nell'automazione, le persone commettono errori.<\/li>\n<li>A volte \u00e8 molto faticoso automatizzare, perch\u00e9 tutto funziona gi\u00e0. Perch\u00e9 migliorare ulteriormente? Perch\u00e9 tutto questo Continuous Integration?<\/li>\n<\/ul>\n<p>\nMa ho delle statistiche: nel 20% delle build vengono rilevati errori. E questo non succede perch\u00e9 i nostri sviluppatori scrivono codice male. Succede perch\u00e9 gli sviluppatori sono sicuri che, se commettono un errore, non arriver\u00e0 in develop, verr\u00e0 catturato dai controlli automatici. Di conseguenza, gli sviluppatori possono dedicare pi\u00f9 tempo alla scrittura di codice e a cose interessanti, piuttosto che eseguire controlli localmente.<\/p>\n<p><strong>Impegnatevi nel Continuous Integration. Ma con moderazione.<\/strong><\/p>\n<blockquote><p>A proposito, Nikolai Nesterov non solo fa ottime presentazioni, ma \u00e8 anche membro del comitato programmatico <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> e aiuta gli altri a preparare interventi significativi per voi. La completezza e l'utilit\u00e0 del programma della prossima conferenza possono essere valutate in base agli argomenti nel <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">calendario<\/a><\/noindex>E per ulteriori dettagli vi aspettiamo il 22-23 aprile all'Infospazio.<\/p><\/blockquote>\n<p>Fonte: <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.0.1 - 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. \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\" \/>\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\/it\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.0.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"it_IT\" \/>\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. \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\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/it\/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\udd47L'evoluzione del CI nel team di sviluppo mobile | ProHoster","description":"Oggi la maggior parte dei prodotti software viene sviluppata in team. Le condizioni per il successo dello sviluppo collaborativo possono essere rappresentate da uno schema semplice. Dopo aver scritto il codice, \u00e8 necessario garantire che sia: Funzionante. Non rompa nulla, compreso il codice scritto dai vostri colleghi. Se entrambe le condizioni sono soddisfatte, siete sulla strada giusta per il successo. Per controllare facilmente queste condizioni e non deviare da...","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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. \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","og:url":"https:\/\/prohoster.info\/it\/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\/it\/wp-json\/wp\/v2\/posts\/31304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=31304"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/31304\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/23270"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=31304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=31304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=31304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}