Evoluzione della CI nel team di sviluppo mobile

Oggi, la maggior parte dei prodotti software vengono sviluppati in team. Le condizioni per uno sviluppo di squadra di successo possono essere rappresentate sotto forma di un semplice diagramma.

Evoluzione della CI nel team di sviluppo mobile

Una volta scritto il codice, devi assicurarti che:

  1. Funziona.
  2. Non rompe nulla, incluso il codice scritto dai tuoi colleghi.

Se entrambe le condizioni sono soddisfatte, allora sei sulla strada del successo. Per verificare facilmente queste condizioni e non deviare dal percorso redditizio, abbiamo ideato l'Integrazione Continua.

CI è un flusso di lavoro in cui integri il tuo codice nel codice prodotto complessivo il più spesso possibile. E non solo integri, ma controlli anche costantemente che tutto funzioni. Dato che è necessario controllare molto e spesso, vale la pena pensare all’automazione. Puoi controllare tutto manualmente, ma non dovresti, ed ecco perché.

  • Gente cara. Un'ora di lavoro di qualsiasi programmatore costa più di un'ora di lavoro di qualsiasi server.
  • Le persone fanno errori. Pertanto, potrebbero verificarsi situazioni in cui i test sono stati eseguiti sul ramo sbagliato o è stato compilato il commit sbagliato per i tester.
  • Le persone sono pigre. Di tanto in tanto, quando finisco un compito, sorge il pensiero: “Cosa c'è da controllare? Ho scritto due righe: funziona tutto! Penso che anche alcuni di voi a volte abbiano pensieri simili. Ma dovresti sempre controllare.

Come è stata implementata e sviluppata l'integrazione continua nel team di sviluppo mobile di Avito, come sono passati da 0 a 450 build al giorno e come le macchine costruiscono assemblano 200 ore al giorno, afferma Nikolai Nesterov (nnesterov) partecipa a tutti i cambiamenti evolutivi dell'applicazione Android CI/CD.

La storia si basa sull'esempio di un comando Android, ma la maggior parte degli approcci sono applicabili anche su iOS.


C'era una volta una persona che lavorava nel team Avito Android. Per definizione non aveva bisogno di nulla dell’Integrazione Continua: non c’era nessuno con cui integrarsi.

Ma l'applicazione è cresciuta, sono apparsi sempre più nuovi compiti e il team è cresciuto di conseguenza. Ad un certo punto, sarà il momento di stabilire in modo più formale un processo di integrazione del codice. Si è deciso di utilizzare il flusso Git.

Evoluzione della CI nel team di sviluppo mobile

Il concetto di flusso Git è ben noto: un progetto ha un ramo di sviluppo comune e, per ogni nuova funzionalità, gli sviluppatori tagliano un ramo separato, si impegnano su di esso, eseguono il push e quando vogliono unire il loro codice nel ramo di sviluppo, aprono un richiesta pull. Per condividere conoscenze e discutere approcci, abbiamo introdotto la revisione del codice, ovvero i colleghi devono verificare e confermare il codice degli altri.

controlli

Vedere il codice con i tuoi occhi è bello, ma non abbastanza. Si stanno quindi introducendo controlli automatici.

  • Prima di tutto controlliamo Assemblea dell'ARCA.
  • Molto Test Junit.
  • Consideriamo la copertura del codice, poiché stiamo eseguendo dei test.

Per capire come dovrebbero essere eseguiti questi controlli, diamo un'occhiata al processo di sviluppo in Avito.

Può essere rappresentato schematicamente in questo modo:

  • Uno sviluppatore scrive codice sul suo laptop. Puoi eseguire i controlli di integrazione proprio qui, con un hook di commit o semplicemente eseguire i controlli in background.
  • Dopo che lo sviluppatore ha inviato il codice, apre una richiesta pull. Affinché il suo codice venga incluso nel ramo di sviluppo, è necessario sottoporsi a una revisione del codice e raccogliere il numero richiesto di conferme. Puoi abilitare controlli e build qui: finché tutte le build non hanno esito positivo, la richiesta pull non può essere unita.
  • Dopo che la richiesta pull è stata unita e il codice è stato incluso nello sviluppo, puoi scegliere un orario conveniente: ad esempio, di notte, quando tutti i server sono liberi, ed eseguire tutti i controlli che desideri.

A nessuno piaceva eseguire scansioni sul proprio laptop. Quando uno sviluppatore ha completato una funzionalità, desidera inviarla rapidamente e aprire una richiesta pull. Se in questo momento vengono lanciati dei controlli lunghi, questo non solo non è molto piacevole, ma rallenta anche lo sviluppo: mentre il laptop controlla qualcosa, è impossibile lavorarci normalmente.

Ci è piaciuto molto eseguire i controlli di notte, perché c'è molto tempo e server, puoi girovagare. Ma, sfortunatamente, quando il codice della funzionalità entra in fase di sviluppo, lo sviluppatore ha molta meno motivazione a correggere gli errori rilevati da CI. Periodicamente mi sorprendevo a pensare, quando esaminavo tutti gli errori rilevati nel rapporto mattutino, che li avrei corretti un giorno o l'altro, perché ora c'è una nuova interessante attività in Jira che voglio semplicemente iniziare a svolgere.

Se i controlli bloccano una richiesta pull, allora c'è abbastanza motivazione, perché finché le build non diventano verdi, il codice non entrerà in fase di sviluppo, il che significa che l'attività non verrà completata.

Di conseguenza, abbiamo scelto la seguente strategia: eseguiamo il maggior numero possibile di controlli di notte e lanciamo quelli più critici e, soprattutto, quelli più veloci su una richiesta pull. Ma non ci fermiamo qui: parallelamente ottimizziamo la velocità dei controlli in modo da trasferirli dalla modalità notturna ai controlli pull request.

A quel tempo, tutte le nostre build venivano completate abbastanza rapidamente, quindi abbiamo semplicemente incluso la build ARK, i test Junit e i calcoli della copertura del codice come blocco per la richiesta pull. L'abbiamo attivato, ci abbiamo pensato e abbiamo abbandonato la copertura del codice perché pensavamo di non averne bisogno.

Ci sono voluti due giorni per impostare completamente l'IC di base (di seguito la stima del tempo è approssimativa, necessaria per la scala).

Successivamente, abbiamo iniziato a pensare ulteriormente: stiamo controllando correttamente? Stiamo eseguendo correttamente le build sulle richieste pull?

Abbiamo iniziato la compilazione sull'ultimo commit del ramo da cui è stata aperta la richiesta pull. Ma i test di questo commit possono solo dimostrare che il codice scritto dallo sviluppatore funziona. Ma non provano che non abbia rotto nulla. In effetti, è necessario controllare lo stato del ramo di sviluppo dopo che una funzionalità è stata incorporata in esso.

Evoluzione della CI nel team di sviluppo mobile

Per fare ciò, abbiamo scritto un semplice script bash premerge.sh:

#!/usr/bin/env bash

set -e

git fetch origin develop

git merge origin/develop

Qui tutte le ultime modifiche apportate allo sviluppo vengono semplicemente richiamate e unite nel ramo corrente. Abbiamo aggiunto lo script premerge.sh come primo passo in tutte le build e abbiamo iniziato a verificare esattamente ciò che vogliamo, ovvero integrazione.

Ci sono voluti tre giorni per localizzare il problema, trovare una soluzione e scrivere questo script.

L'applicazione si è sviluppata, sono apparse sempre più attività, il team è cresciuto e premerge.sh a volte ha iniziato a deluderci. Lo sviluppo ha subito modifiche contrastanti che hanno interrotto la build.

Un esempio di come ciò accade:

Evoluzione della CI nel team di sviluppo mobile

Due sviluppatori iniziano a lavorare contemporaneamente sulle funzionalità A e B. Lo sviluppatore della funzionalità A scopre una funzionalità inutilizzata nel progetto answer() e, da bravo boy scout, lo toglie. Allo stesso tempo, lo sviluppatore della funzionalità B aggiunge una nuova chiamata a questa funzione nel suo ramo.

Gli sviluppatori terminano il loro lavoro e contemporaneamente aprono una richiesta pull. Le build vengono avviate, premerge.sh controlla entrambe le richieste pull relative all'ultimo stato di sviluppo: tutti i controlli sono verdi. Successivamente, la richiesta pull della funzione A viene unita, la richiesta pull della funzione B viene unita... Boom! Lo sviluppo si interrompe perché il codice di sviluppo contiene una chiamata a una funzione inesistente.

Evoluzione della CI nel team di sviluppo mobile

Quando non si svilupperà, lo è disastro locale. L'intero team non può raccogliere nulla e inviarlo per i test.

È successo che molto spesso lavoravo su attività infrastrutturali: analisi, rete, database. Cioè, sono stato io a scrivere quelle funzioni e classi utilizzate da altri sviluppatori. Per questo motivo mi sono trovato molto spesso in situazioni simili. Ho anche tenuto questa foto appesa per un po'.

Evoluzione della CI nel team di sviluppo mobile

Poiché questo non ci soddisfaceva, abbiamo iniziato a esplorare le opzioni su come prevenirlo.

Come non rompere lo sviluppo

La prima opzione: ricostruire tutte le richieste pull durante l'aggiornamento dello sviluppo. Se, nel nostro esempio, la pull request con la feature A è la prima ad essere inclusa in development, la pull request della feature B verrà ricostruita e, di conseguenza, i controlli falliranno a causa di un errore di compilazione.

Per capire quanto tempo ci vorrà, considera un esempio con due PR. Apriamo due PR: due build, due run di controlli. Dopo che il primo PR è stato incorporato nello sviluppo, il secondo deve essere ricostruito. In totale, due PR richiedono tre serie di prove: 2 + 1 = 3.

In linea di principio, va bene. Ma abbiamo guardato le statistiche, e la situazione tipica nel nostro team era di 10 PR aperti, e quindi il numero di controlli è la somma della progressione: 10 + 9 +... + 1 = 55. Cioè, accettare 10 PR, devi ricostruire 55 volte. E questo è in una situazione ideale, quando tutti i controlli vengono superati la prima volta, quando nessuno apre un'ulteriore richiesta pull mentre queste dozzine vengono elaborate.

Immagina di essere uno sviluppatore che deve essere il primo a fare clic sul pulsante "unisci", perché se un vicino lo fa, dovrai aspettare finché tutte le build verranno nuovamente eseguite... No, non funzionerà , rallenterà seriamente lo sviluppo.

Secondo modo possibile: raccogliere richieste pull dopo la revisione del codice. Cioè, apri una richiesta pull, raccogli il numero richiesto di approvazioni dai colleghi, correggi ciò che è necessario e quindi avvii le build. Se hanno esito positivo, la richiesta pull viene unita allo sviluppo. In questo caso non ci sono riavvii aggiuntivi, ma il feedback è notevolmente rallentato. Come sviluppatore, quando apro una richiesta pull, voglio immediatamente vedere se funzionerà. Ad esempio, se un test fallisce, è necessario correggerlo rapidamente. Nel caso di una build ritardata, il feedback rallenta, e quindi l’intero sviluppo. Anche questo non andava bene per noi.

Di conseguenza, rimaneva solo la terza opzione: Ciclismo. Tutto il nostro codice, tutti i nostri sorgenti sono archiviati in un repository sul server Bitbucket. Di conseguenza, abbiamo dovuto sviluppare un plugin per Bitbucket.

Evoluzione della CI nel team di sviluppo mobile

Questo plugin sovrascrive il meccanismo di fusione delle richieste pull. L'inizio è standard: si apre il PR, vengono avviati tutti gli assembly, la revisione del codice è completata. Ma dopo che la revisione del codice è stata completata e lo sviluppatore ha deciso di fare clic su “unisci”, il plugin verifica su quale stato di sviluppo sono stati eseguiti i controlli. Se development è stato aggiornato dopo le build, il plugin non consentirà l'unione di tale richiesta pull nel ramo principale. Riavvierà semplicemente le build di uno sviluppo relativamente recente.

Evoluzione della CI nel team di sviluppo mobile

Nel nostro esempio con modifiche contrastanti, tali build falliranno a causa di un errore di compilazione. Di conseguenza, lo sviluppatore della funzionalità B dovrà correggere il codice, riavviare i controlli, quindi il plugin applicherà automaticamente la richiesta pull.

Prima di implementare questo plugin, avevamo una media di 2,7 revisioni per richiesta pull. Con il plugin ci sono stati 3,6 lanci. Questo ci andava bene.

Vale la pena notare che questo plugin ha uno svantaggio: riavvia la build solo una volta. Cioè, c'è ancora una piccola finestra attraverso la quale possono svilupparsi cambiamenti contrastanti. Ma la probabilità che ciò accada è bassa e abbiamo trovato un compromesso tra il numero di avvii e la probabilità di fallimento. In due anni ha sparato solo una volta, quindi probabilmente non è stato invano.

Ci sono volute due settimane per scrivere la prima versione del plugin Bitbucket.

Nuovi controlli

Nel frattempo, il nostro team ha continuato a crescere. Sono stati aggiunti nuovi controlli.

Abbiamo pensato: perché commettere errori se possono essere prevenuti? Ed è per questo che l'hanno implementato analisi del codice statico. Abbiamo iniziato con lint, incluso nell'SDK di Android. Ma a quel tempo non sapeva affatto come lavorare con il codice Kotlin e avevamo già il 75% dell'applicazione scritta in Kotlin. Pertanto, quelli integrati sono stati aggiunti a lint Verifiche di Android Studio.

Per fare questo, abbiamo dovuto fare molte perversioni: prendere Android Studio, pacchettizzarlo in Docker ed eseguirlo su CI con un monitor virtuale, in modo che pensi di funzionare su un vero laptop. Ma ha funzionato.

È stato anche in questo periodo che abbiamo iniziato a scrivere molto prove di strumentazione e implementato test degli screenshot. Questo avviene quando viene generato uno screenshot di riferimento per una piccola visualizzazione separata e il test consiste nel prendere uno screenshot dalla visualizzazione e confrontarlo direttamente pixel per pixel con lo standard. Se c'è una discrepanza significa che il layout è andato storto da qualche parte oppure c'è qualcosa che non va negli stili.

Ma i test di strumentazione e i test sugli screenshot devono essere eseguiti sui dispositivi: su emulatori o su dispositivi reali. Considerando che i test sono molti e vengono eseguiti frequentemente, è necessaria un'intera azienda agricola. Avviare la propria fattoria richiede troppo lavoro, quindi abbiamo trovato un'opzione già pronta: Firebase Test Lab.

Laboratorio di prova Firebase

È stato scelto perché Firebase è un prodotto Google, il che significa che dovrebbe essere affidabile e difficilmente destinato a morire. I prezzi sono ragionevoli: 5$ per ora di funzionamento di un dispositivo reale, 1$ per ora di funzionamento di un emulatore.

Ci sono volute circa tre settimane per implementare Firebase Test Lab nella nostra CI.

Ma il team ha continuato a crescere e, sfortunatamente, Firebase ha iniziato a deluderci. A quel tempo non aveva alcuno SLA. A volte Firebase ci ha fatto attendere finché il numero richiesto di dispositivi fosse libero per i test e non ha iniziato a eseguirli immediatamente, come avremmo voluto. L'attesa in fila è durata fino a mezz'ora, il che è un tempo molto lungo. Sono stati eseguiti test di strumentazione su ogni PR, i ritardi hanno rallentato notevolmente lo sviluppo e quindi la fattura mensile è arrivata con una somma tonda. In generale, si è deciso di abbandonare Firebase e lavorare internamente, poiché il team era cresciuto abbastanza.

Docker + Python + bash

Abbiamo preso Docker, ci abbiamo inserito degli emulatori, abbiamo scritto un semplice programma in Python, che al momento giusto fa apparire il numero richiesto di emulatori nella versione richiesta e li ferma quando necessario. E, naturalmente, un paio di script bash: dove saremmo senza di loro?

Ci sono volute cinque settimane per creare il nostro ambiente di test.

Di conseguenza, per ogni richiesta pull era presente un ampio elenco di controlli per il blocco dell'unione:

  • assemblaggio dell'ARCA;
  • Test Junit;
  • pelucchi;
  • Verifiche di Android Studio;
  • Prove di strumentazione;
  • Prove di screenshot.

Ciò ha impedito molti possibili guasti. Tecnicamente tutto ha funzionato, ma gli sviluppatori si sono lamentati del fatto che l'attesa per i risultati fosse troppo lunga.

Quanto lungo è troppo lungo? Abbiamo caricato i dati da Bitbucket e TeamCity nel sistema di analisi e ce ne siamo resi conto tempo medio di attesa 45 minuti. Cioè uno sviluppatore, quando apre una richiesta pull, attende in media 45 minuti per i risultati della build. Secondo me questo è molto e non puoi lavorare così.

Naturalmente, abbiamo deciso di accelerare tutte le nostre build.

Acceleriamo

Dato che le build spesso stanno in coda, la prima cosa che facciamo è acquistato più hardware — lo sviluppo estensivo è il più semplice. Le build hanno smesso di fare la coda, ma il tempo di attesa è diminuito solo leggermente, perché alcuni controlli stessi richiedevano molto tempo.

Rimozione dei controlli che richiedono troppo tempo

La nostra integrazione continua potrebbe rilevare questo tipo di errori e problemi.

  • Non lo farò. L'interfaccia CI può rilevare un errore di compilazione quando qualcosa non viene creato a causa di modifiche contrastanti. Come ho già detto, nessuno riesce ad assemblare nulla, lo sviluppo si ferma e tutti si innervosiscono.
  • Bug nel comportamento. Ad esempio, quando l'applicazione viene creata, ma si blocca quando si preme un pulsante o il pulsante non viene premuto affatto. Questo è negativo perché un bug di questo tipo può raggiungere l'utente.
  • Bug nel layout. Ad esempio, viene fatto clic su un pulsante, ma si è spostato di 10 pixel a sinistra.
  • Aumento del debito tecnico.

Dopo aver esaminato questo elenco, ci siamo resi conto che solo i primi due punti sono critici. Vogliamo prima affrontare questi problemi. I bug nel layout vengono scoperti nella fase di revisione del progetto e possono essere facilmente corretti in seguito. Gestire il debito tecnico richiede un processo e una pianificazione separati, quindi abbiamo deciso di non testarlo su una richiesta pull.

Sulla base di questa classificazione, abbiamo scosso l'intero elenco dei controlli. Lint barrato e ne ha rinviato il lancio dall'oggi al domani: giusto per produrre un resoconto su quanti problemi c'erano nel progetto. Abbiamo concordato di lavorare separatamente con il debito tecnico e I controlli di Android Studio sono stati completamente abbandonati. Android Studio in Docker per l'esecuzione delle ispezioni sembra interessante, ma causa molti problemi di supporto. Qualsiasi aggiornamento alle versioni di Android Studio significa lottare contro bug incomprensibili. Inoltre era difficile supportare i test degli screenshot, perché la libreria non era molto stabile e c'erano falsi positivi. I test degli screenshot sono stati rimossi dall'elenco di controllo.

Di conseguenza, ci è rimasto:

  • assemblaggio dell'ARCA;
  • Test Junit;
  • Prove di strumentazione.

Cache remota Gradle

Senza controlli pesanti, tutto è diventato migliore. Ma non c'è limite alla perfezione!

La nostra applicazione era già suddivisa in circa 150 moduli graduali. La cache remota di Gradle di solito funziona bene in questo caso, quindi abbiamo deciso di provarla.

La cache remota di Gradle è un servizio che può memorizzare nella cache gli artefatti di build per singole attività in singoli moduli. Gradle, invece di compilare effettivamente il codice, utilizza HTTP per bussare alla cache remota e chiedere se qualcuno ha già eseguito questa attività. Se sì, scarica semplicemente il risultato.

Eseguire la cache remota di Gradle è semplice perché Gradle fornisce un'immagine Docker. Siamo riusciti a farlo in tre ore.

Tutto quello che dovevi fare era avviare Docker e scrivere una riga nel progetto. Ma anche se può essere lanciato rapidamente, ci vorrà molto tempo perché tutto funzioni bene.

Di seguito è riportato il grafico dei mancati risultati della cache.

Evoluzione della CI nel team di sviluppo mobile

All'inizio, la percentuale di cache miss era di circa 65. Dopo tre settimane siamo riusciti ad aumentare questo valore al 20%. Si è scoperto che le attività raccolte dall'applicazione Android hanno strane dipendenze transitive, a causa delle quali Gradle ha mancato la cache.

Collegando la cache abbiamo velocizzato notevolmente la compilazione. Ma oltre al montaggio ci sono anche i test strumentali, e richiedono molto tempo. Forse non è necessario eseguire tutti i test per ogni richiesta pull. Per scoprirlo, utilizziamo l’analisi d’impatto.

Analisi d'impatto

Su una richiesta pull, raccogliamo git diff e troviamo i moduli Gradle modificati.

Evoluzione della CI nel team di sviluppo mobile

È opportuno eseguire solo test della strumentazione che controllino i moduli modificati e tutti i moduli che dipendono da essi. Non ha senso eseguire test sui moduli vicini: il codice lì non è cambiato e nulla può rompersi.

I test di strumentazione non sono così semplici, perché devono essere posizionati nel modulo Applicazione di livello superiore. Abbiamo utilizzato l'euristica con l'analisi del bytecode per capire a quale modulo appartiene ciascun test.

L'aggiornamento del funzionamento dei test della strumentazione in modo che vengano testati solo i moduli coinvolti ha richiesto circa otto settimane.

Le misure volte ad accelerare le ispezioni hanno funzionato con successo. Dai 45 minuti siamo passati a circa 15. Già è normale aspettare un quarto d’ora per una build.

Ma ora gli sviluppatori hanno iniziato a lamentarsi di non capire quali build vengono lanciate, dove vedere il registro, perché la build è rossa, quale test ha fallito, ecc.

Evoluzione della CI nel team di sviluppo mobile

Problemi con il feedback rallentano lo sviluppo, quindi abbiamo cercato di fornire informazioni quanto più chiare e dettagliate possibile su ogni PR e build. Abbiamo iniziato con commenti in Bitbucket al PR, indicando quale build aveva fallito e perché, e abbiamo scritto messaggi mirati in Slack. Alla fine, abbiamo creato una dashboard PR per la pagina con un elenco di tutte le build attualmente in esecuzione e il loro stato: in coda, in esecuzione, bloccato o completato. È possibile fare clic sulla build e accedere al relativo registro.

Evoluzione della CI nel team di sviluppo mobile

Sono state dedicate sei settimane al feedback dettagliato.

Piani

Passiamo alla storia recente. Dopo aver risolto il problema del feedback, abbiamo raggiunto un nuovo livello: abbiamo deciso di creare la nostra fattoria di emulatori. Quando ci sono molti test ed emulatori, sono difficili da gestire. Di conseguenza, tutti i nostri emulatori sono passati al cluster k8s con una gestione flessibile delle risorse.

Inoltre ci sono altri piani.

  • Restituisci lanugine (e altre analisi statiche). Stiamo già lavorando in questa direzione.
  • Esegui tutto su un blocco PR test end-to-end su tutte le versioni dell'SDK.

Quindi, abbiamo tracciato la storia dello sviluppo dell'integrazione continua in Avito. Ora voglio dare qualche consiglio da un punto di vista esperto.

Suggerimenti

Se potessi dare solo un consiglio sarebbe questo:

Si prega di fare attenzione con gli script di shell!

Bash è uno strumento molto flessibile e potente, è molto comodo e veloce scrivere script. Ma con questo si può cadere in una trappola e, sfortunatamente, ci siamo caduti noi.

Tutto è iniziato con semplici script eseguiti sulle nostre macchine di costruzione:

#!/usr/bin/env bash
./gradlew assembleDebug

Ma, come sai, tutto si sviluppa e diventa più complicato nel tempo - eseguiamo uno script da un altro, passiamo lì alcuni parametri - alla fine abbiamo dovuto scrivere una funzione che determina a quale livello di annidamento bash siamo ora in ordine per inserire le virgolette necessarie, per dare inizio al tutto.

Evoluzione della CI nel team di sviluppo mobile

Potete immaginare il costo della manodopera per lo sviluppo di tali script. Ti consiglio di non cadere in questa trappola.

Cosa può essere sostituito?

  • Qualsiasi linguaggio di scripting. Scrivere a Script Python o Kotlin più conveniente perché si tratta di programmazione, non di script.
  • Oppure descrivi tutta la logica di compilazione nel modulo Attività di livello personalizzate per il tuo progetto

Abbiamo deciso di scegliere la seconda opzione e ora stiamo eliminando sistematicamente tutti gli script bash e scrivendo molte attività gradle personalizzate.

Suggerimento n. 2: archivia l'infrastruttura nel codice.

È conveniente quando l'impostazione dell'integrazione continua non viene archiviata nell'interfaccia utente di Jenkins o TeamCity, ecc., ma sotto forma di file di testo direttamente nel repository del progetto. Ciò garantisce la possibilità di versione. Non sarà difficile eseguire il rollback o creare il codice su un altro ramo.

Gli script possono essere archiviati in un progetto. Cosa fare con l'ambiente?

Suggerimento n. 3: Docker può aiutare a proteggere l'ambiente.

Aiuterà sicuramente gli sviluppatori Android; iOS non ne ha ancora uno, sfortunatamente.

Questo è un esempio di un semplice file docker che contiene jdk e android-sdk:

FROM openjdk:8

ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip" 
    ANDROID_HOME="/usr/local/android-sdk" 
    ANDROID_VERSION=26 
    ANDROID_BUILD_TOOLS_VERSION=26.0.2

# Download Android SDK
RUN mkdir "$ANDROID_HOME" .android 
    && cd "$ANDROID_HOME" 
    && curl -o sdk.zip $SDK_URL 
    && unzip sdk.zip 
    && rm sdk.zip 
    && yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses

# Install Android Build Tool and Libraries
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" 
    "platforms;android-${ANDROID_VERSION}" 
    "platform-tools"

RUN mkdir /application
WORKDIR /application

Dopo aver scritto questo file Docker (ti svelo un segreto, non devi scriverlo, ma basta estrarlo già pronto da GitHub) e assemblato l'immagine, ottieni una macchina virtuale su cui puoi costruire l'applicazione ed eseguire i test Junit.

I due motivi principali per cui ciò ha senso sono la scalabilità e la ripetibilità. Usando la finestra mobile, puoi creare rapidamente una dozzina di agenti di compilazione che avranno esattamente lo stesso ambiente del precedente. Ciò rende la vita degli ingegneri CI molto più semplice. È abbastanza semplice inserire l'SDK di Android nella finestra mobile, ma con gli emulatori è un po' più difficile: dovrai lavorare un po' di più (o scaricare di nuovo quello finito da GitHub).

Suggerimento n. 4: non dimenticare che le ispezioni non vengono effettuate per il bene delle ispezioni, ma per le persone.

Un feedback rapido e, soprattutto, chiaro è molto importante per gli sviluppatori: cosa si è rotto, quale test ha fallito, dove posso vedere il buildlog.

Suggerimento n. 5: sii pragmatico quando sviluppi l'integrazione continua.

Comprendi chiaramente quali tipi di errori vuoi prevenire, quante risorse, tempo e tempo al computer sei disposto a spendere. I controlli che impiegano troppo tempo possono, ad esempio, essere rinviati dall’oggi al domani. E quelli che rilevano errori non molto importanti dovrebbero essere completamente abbandonati.

Suggerimento n. 6: utilizzare strumenti già pronti.

Oggi sono molte le aziende che forniscono CI cloud.

Evoluzione della CI nel team di sviluppo mobile

Questa è una buona soluzione per piccoli team. Non è necessario supportare nulla, basta pagare un po' di soldi, creare la tua applicazione e persino eseguire test della strumentazione.

Suggerimento n. 7: in un team numeroso, le soluzioni interne sono più redditizie.

Ma prima o poi, man mano che il team cresce, le soluzioni interne diventeranno più redditizie. C’è un problema con queste decisioni. Esiste una legge dei rendimenti decrescenti in economia: in qualsiasi progetto, ogni miglioramento successivo è sempre più difficile e richiede sempre più investimenti.

L’economia descrive tutta la nostra vita, inclusa l’integrazione continua. Ho creato un programma dei costi di manodopera per ogni fase di sviluppo della nostra integrazione continua.

Evoluzione della CI nel team di sviluppo mobile

È chiaro che qualsiasi miglioramento sta diventando sempre più difficile. Guardando questo grafico, puoi capire che l'integrazione continua deve essere sviluppata in conformità con la crescita delle dimensioni del team. Per un team di due persone, dedicare 50 giorni allo sviluppo di una farm di emulatori interni è un'idea mediocre. Ma allo stesso tempo, per un team numeroso, anche non fare affatto l’integrazione continua è una cattiva idea, perché problemi di integrazione, problemi di comunicazione, ecc. ci vorrà ancora più tempo.

Siamo partiti dall’idea che l’automazione è necessaria perché le persone sono costose, commettono errori e sono pigre. Ma le persone automatizzano anche. Pertanto, tutti gli stessi problemi si applicano all'automazione.

  • L’automazione è costosa. Ricorda il programma del travaglio.
  • Quando si parla di automazione, le persone commettono errori.
  • A volte è molto pigro automatizzare, perché tutto funziona in questo modo. Perché migliorare qualcos'altro, perché tutta questa integrazione continua?

Ma ho delle statistiche: gli errori vengono rilevati nel 20% degli assemblaggi. E questo non è perché i nostri sviluppatori scrivono male il codice. Questo perché gli sviluppatori sono sicuri che se commettono qualche errore, questo non finirà in fase di sviluppo, ma verrà rilevato da controlli automatizzati. Di conseguenza, gli sviluppatori possono dedicare più tempo alla scrittura di codice e cose interessanti, piuttosto che eseguire e testare qualcosa a livello locale.

Pratica l'integrazione continua. Ma con moderazione.

A proposito, Nikolai Nesterov non solo fornisce ottimi resoconti, ma è anche membro del comitato di programma AppConf e aiuta gli altri a preparare discorsi significativi per te. La completezza e l'utilità del programma del prossimo convegno potranno essere valutate in base agli argomenti in orario. E per i dettagli, vieni su Infospace il 22 e 23 aprile.

Fonte: habr.com

Aggiungi un commento