L'evoluzione CI nel team di sviluppo mobile

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.

L'evoluzione CI nel team di sviluppo mobile

Dopo aver scritto il codice, devi assicurarti che esso:

  1. Funzioni.
  2. Non rompa nulla, compreso il codice scritto dai tuoi colleghi.

Se entrambe le condizioni sono soddisfatte, sei sulla strada del successo. Per controllare facilmente queste condizioni e non deviare da un percorso vantaggioso, è stata ideata l'Integrazione Continua.

La CI è un flusso di lavoro in cui integri il tuo codice nel codice comune del prodotto il più frequentemente possibile. E non solo integri, ma verifichi anche costantemente che tutto funzioni. Poiché le verifiche devono essere molte e frequenti, è il momento di considerare l'automazione. Puoi controllare tutto manualmente, ma non dovresti, ecco perché.

  • Le persone costano care. L'ora di lavoro di qualsiasi programmatore costa più dell'ora di lavoro di qualsiasi server.
  • Le persone commettono errori. Pertanto, possono sorgere situazioni in cui i test vengono eseguiti su un ramo sbagliato o viene raccolto un commit errato per i tester.
  • Le persone sono pigre. Di tanto in tanto, quando concludo un compito, mi viene in mente: «Ma che cosa c'è da controllare? Ho scritto due righe — sicuramente funziona tutto!» Credo che anche alcuni di voi abbiano questi pensieri di tanto in tanto. Ma bisogna sempre controllare.

Come 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 (nnesterov) — partecipante a tutti i cambiamenti evolutivi del CI/CD dell'app Android.

Il racconto si basa sull'esempio del team Android, ma la maggior parte degli approcci è applicabile anche su iOS.

Riproduci video

Un tempo, nel team Android di Avito, lavorava una sola persona. Di per sé, non aveva bisogno di nulla del Continuous Integration: non c'era nessuno con cui integrarsi.

Ma l'app cresceva, comparivano sempre più nuove attività e, di conseguenza, il team cresceva. A un certo punto, è arrivato il momento di organizzare in modo più formale il processo di integrazione del codice. Si è deciso di utilizzare Git flow.

L'evoluzione CI nel team di sviluppo mobile

Il concetto di Git flow è noto: nel progetto c'è un ramo comune chiamato develop, e per ogni nuova funzionalità 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.

Verifiche

Esaminare il codice visivamente è utile, ma non sufficiente. Pertanto, vengono introdotte verifiche automatiche.

  • Per prima cosa verifichiamo la build ARK.
  • Molti test Junit.
  • Contiamo la code coverage, visto che stiamo eseguendo i test.

Per capire come dovremmo eseguire queste verifiche, guardiamo al processo di sviluppo in Avito.

Schematicamente, può essere rappresentato così:

  • Lo sviluppatore scrive codice sul proprio laptop. Può eseguire le verifiche di integrazione proprio qui — oppure tramite hook di commit o semplicemente eseguendo le verifiche in background.
  • Dopo che lo sviluppatore ha spinto il codice, apre una pull request. Affinché 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é tutte le build non sono riuscite, non si può unire la pull request.
  • Dopo che la pull request è stata unita e il codice è passato a develop, si può scegliere un momento conveniente: ad esempio, di notte, quando i server sono liberi, e lanciare quanti più controlli possibile.

A 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 è sgradevole, ma rallenta lo sviluppo: mentre il laptop effettua il controllo, non è possibile lavorare normalmente.

Ci è piaciuto molto lanciare i controlli di notte, perché ci sono tempi e server a disposizione, si può fare un bel lavoro. Purtroppo, quando il codice della funzione è passato a develop, lo sviluppatore ha già 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ù tardi, perché ora c'è un compito interessante in Jira che voglio iniziare a fare.

Se i controlli bloccano la pull request, la motivazione è sufficiente, perché finché i build non diventano verdi, il codice non passerà a develop, e quindi il compito non sarà completato.

Alla fine abbiamo scelto una strategia del genere: durante la notte eseguiamo il massimo possibile di controlli, mentre i più critici e, soprattutto i più veloci, li lanciamo al momento della pull request. Ma non ci fermiamo qui: stiamo parallelamente ottimizzando la velocità dei controlli per poterli trasferire dal regime notturno ai controlli sulle pull request.

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é abbiamo ritenuto che non fosse necessaria.

Ci sono voluti due giorni per configurare il CI di base (qui e di seguito, la stima temporale è approssimativa e serve a dare un'idea della scala).

Dopo ciò, abbiamo cominciato a chiederci: stiamo controllando correttamente? Stiamo lanciando le build sulla pull request in modo corretto?

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à, è necessario controllare lo stato del ramo develop dopo che la funzionalità è stata integrata in esso.

L'evoluzione CI nel team di sviluppo mobile

Per questo abbiamo scritto un semplice script bash premerge.sh:

#!/usr/bin/env bash

set -e

git fetch origin develop

git merge origin/develop

Qui si recuperano semplicemente tutte le modifiche più 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ò che vogliamo, ovvero integrazione.

Ci sono voluti tre giorni per localizzare i problemi, cercare soluzioni e scrivere questo script.

L'applicazione si è evoluta, le attività aumentavano, il team cresceva e premerge.sh ha iniziato a darci problemi. Modifiche conflittuali infiltravano develop e rompevano la build.

Un esempio di come ciò accade:

L'evoluzione CI nel team di sviluppo mobile

Due sviluppatori iniziano contemporaneamente a lavorare sulle funzionalità A e B. Lo sviluppatore della funzionalità A scopre una funzione non utilizzata nel progetto answer() e, da buon scout, la rimuove. Nel frattempo, lo sviluppatore della funzionalità B aggiunge una nuova chiamata a questa funzione nel proprio ramo.

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 — tutti i controlli sono verdi. Dopodiché viene fuso il pull request della funzionalità A, viene fuso il pull request della funzionalità B… Boom! Develop si rompe, perché nel codice di develop c'è una chiamata a una funzione inesistente.

L'evoluzione CI nel team di sviluppo mobile

Quando non si riesce a sviluppare, è una catastrofe locale. L'intero team non riesce a compilare e a consegnare nulla per i test.

È successo che mi sono occupato più frequentemente di compiti infrastrutturali: analisi, rete, basi di dati. Cioè, 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.

L'evoluzione CI nel team di sviluppo mobile

Poiché questo non ci soddisfaceva, abbiamo iniziato a lavorare su opzioni per prevenire ciò.

Come non rompere develop

Prima opzione: ricompilare tutte le pull request quando si aggiorna develop. Se nel nostro esempio la pull request della funzionalità A arriva per prima in develop, la pull request della funzionalità B deve essere ricompilata, e, di conseguenza, non supererà i controlli a causa di un errore di compilazione.

Per capire quanto tempo ci vorrà, consideriamo un esempio con due PR. Apriamo due PR: due build, due avvii di controlli. Dopo che il primo PR è stato integrato in develop, il secondo deve essere ricompilato. In totale, per due PR occorrono tre avvii di controlli: 2 + 1 = 3.

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.

Immagina di essere uno sviluppatore che deve premere il pulsante "merge" per primo, perché se lo fa il tuo vicino, dovrai aspettare che tutte le compilazioni vengano eseguite da capo... No, così non va, rallenterà seriamente lo sviluppo.

Un secondo possibile modo: compilare la pull request dopo la revisione del codice. 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 è 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 è rallentato, e di conseguenza l'intero processo di sviluppo ne risente. Anche a noi non piaceva questo.

Di conseguenza, è rimasta solo la terza opzione — inventare di nuovo. Tutto il nostro codice, tutti i nostri sorgenti sono memorizzati nel repository sul server Bitbucket. Pertanto, abbiamo dovuto sviluppare un plugin per Bitbucket.

L'evoluzione CI nel team di sviluppo mobile

Questo plugin sovrascrive il meccanismo di unione delle pull request. L'inizio è standard: si apre una PR, vengono eseguiti tutti i build, e si svolge la revisione del codice. Tuttavia, una volta che la revisione del codice è stata completata e lo sviluppatore decide di premere «merge», il plugin verifica in quale stato si trovava il branch develop al momento delle verifiche. Se dopo i build develop è stato aggiornato, il plugin non permetterà di unire tale pull request nel branch principale. Riavvierà semplicemente i build rispetto al develop aggiornato.

L'evoluzione CI nel team di sviluppo mobile

Nel nostro esempio con le modifiche in conflitto, tali build non passeranno a causa di un errore di compilazione. Di conseguenza, lo sviluppatore della funzionalità B dovrà correggere il codice e riavviare le verifiche, quindi il plugin applicherà automaticamente la pull request.

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.

È 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à che ciò accada è bassa, e quindi abbiamo accettato questo compromesso tra il numero di esecuzioni e la possibilità di problemi. In due anni è successo solo una volta, quindi probabilmente non è stato vano.

La scrittura della prima versione del plugin per Bitbucket ci ha richiesto due settimane.

Nuove verifiche

Nel frattempo, il nostro team ha continuato a crescere. Sono state aggiunte nuove verifiche.

Abbiamo pensato: perché riparare gli errori, se possiamo prevenirli? E per questo abbiamo implementato senza eseguirlo, sia un controllo durante l'esecuzione (. Siamo partiti da lint, che era incluso nell'Android SDK. Ma all'epoca non era in grado di lavorare con codice Kotlin, e già il 75% dell'applicazione era scritto in Kotlin. Così sono stati aggiunti a lint dei controlli integrati di Android Studio.

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.

In questo periodo abbiamo anche iniziato a scrivere molti test di strumentazione e abbiamo implementato il testing con screenshot.. 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 è andata in tilt o c'è qualcosa di sbagliato negli stili.

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, è necessaria un'intera fattoria. Creare la propria fattoria è troppo dispendioso in termini di tempo, quindi abbiamo trovato una soluzione pronta: Firebase Test Lab.

Firebase Test Lab

È stato scelto perché Firebase è un prodotto Google, quindi dovrebbe essere affidabile e difficilmente smetterà 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.

Ci sono volute circa tre settimane per integrare Firebase Test Lab nel nostro CI.

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, è stato deciso di rinunciare a Firebase e di sviluppare in house, dato che il team era cresciuto abbastanza.

Docker + Python + bash

Abbiamo 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 — come potrebbe mancare?

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

Di conseguenza, ogni pull request aveva una lunga lista di controlli che bloccava la fusione:

  • Costruzione ARK;
  • Test Junit;
  • Lint;
  • Controlli di Android Studio;
  • Test di strumentazione;
  • Test di screenshot.

Questo ha prevenuto molti possibili guasti. Tecnicamente tutto funzionava, ma gli sviluppatori si lamentavano che i tempi di attesa per i risultati erano troppo lunghi.

Troppo lungo — quanto? Abbiamo esportato i dati da Bitbucket e TeamCity nel sistema di analisi e abbiamo capito che il tempo medio di attesa è di 45 minuti. Quindi, uno sviluppatore che apre una pull request attende in media i risultati delle build per 45 minuti. A mio parere, è davvero troppo, e non possiamo lavorare in questo modo.

Naturalmente, abbiamo deciso di accelerare tutte le nostre build.

Acceleriamo

Vedendo che le build spesso erano in coda, abbiamo prima di tutto acquistato ulteriore hardware — lo sviluppo estensivo è la soluzione più semplice. Le build hanno smesso di rimanere in coda, ma il tempo di attesa è diminuito solo di poco, perché alcune verifiche richiedevano comunque molto tempo.

Eliminiamo i controlli troppo lunghi

Il nostro Continuous Integration poteva catturare tali tipi di errori e problemi.

  • Non si compila. CI può intercettare un errore di compilazione quando, a causa di modifiche conflittuali, qualcosa non si compila. Come ho già detto, in quel momento nessuno può compilare nulla, lo sviluppo si ferma e tutti sono nervosi.
  • Un bug nel comportamento. Ad esempio, quando l'app si blocca al clic su un pulsante o il pulsante non risponde affatto. Questo è un problema, poiché un bug simile può arrivare all'utente.
  • Bug nel layout. Ad esempio, il pulsante si può cliccare, ma è spostato di 10 pixel a sinistra.
  • Aumento del debito tecnico.

Esaminando 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.

Sulla base di questa classificazione, abbiamo esaminato l'intero elenco di controlli. Abbiamo escluso Lint 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 abbiamo completamente abbandonato i controlli di Android Studio. Android Studio in Docker per eseguire ispezioni sembra interessante, ma causa molti problemi di supporto. Qualsiasi aggiornamento delle versioni di Android Studio è una lotta contro bug incomprensibili. È stato altrettanto difficile mantenere i test degli screenshot, perché la libreria non era molto stabile, si verificavano falsi positivi. I test degli screenshot sono stati rimossi dalla lista di controlli.

Alla fine ci sono rimasti:

  • Costruzione ARK;
  • Test Junit;
  • Test di strumentazione.

Cache remota di Gradle

Senza controlli pesanti, tutto è migliorato. Ma non c'è limite al miglioramento!

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

La cache remota di Gradle è un servizio che può 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à eseguito quel compito. Se sì, scarica semplicemente il risultato.

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

È bastato avviare Docker e inserire una riga nel progetto. Anche se avviarlo è rapido, far funzionare tutto bene richiederà abbastanza tempo.

Di seguito il grafico dei cache misses.

L'evoluzione CI nel team di sviluppo mobile

All'inizio, il tasso di miss del cache era intorno al 65%. Dopo tre settimane, siamo riusciti a ridurre questa cifra al 20%. Si è scoperto che i task raccolti dall'applicazione Android hanno strane dipendenze transitiviche, a causa delle quali Gradle falliva nel colpire il cache.

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.

Analisi d'impatto

Nella pull request raccogliamo il git diff e troviamo i moduli Gradle modificati.

L'evoluzione CI nel team di sviluppo mobile

Ha 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 è cambiato e nulla può rompersi.

I test di instrumentation non sono così semplici, poiché devono trovarsi nel modulo di livello più alto dell'Application. Abbiamo applicato un'euristica con analisi del bytecode per comprendere a quale modulo appartiene ciascun test.

La modernizzazione del funzionamento dei test di instrumentation, in modo che verifichino solo i moduli coinvolti, ha richiesto circa otto settimane.

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 è diventato normale.

Ma ora gli sviluppatori hanno iniziato a lamentarsi che non è chiaro quali build siano in corso, dove guardare i log, perché il build è fallito, quale test ha dato errore, ecc.

L'evoluzione CI nel team di sviluppo mobile

I 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 è fallita e perché, 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. È possibile cliccare su un build per accedere al suo log.

L'evoluzione CI nel team di sviluppo mobile

Per fornire un feedback dettagliato sono state necessarie sei settimane.

Piani

Passiamo alla nostra storia più 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.

Inoltre, ci sono altri piani.

  • Restituire Lint (e altri analizzatori statici). Stiamo già lavorando in questa direzione.
  • Eseguire tutti i test end-to-end su tutte le versioni dell'SDK. Quindi, abbiamo tracciato la storia dello sviluppo della Continuous Integration in Avito. Ora voglio dare alcuni consigli da esperto.

Se potessi dare solo un consiglio, sarebbe questo:

Consigli

Per favore, abbiate cura con gli script shell!

Bash è uno strumento molto flessibile e potente, ed è molto comodo e veloce scrivere script. Ma ci si può incappare in trappole, e purtroppo ci siamo caduti.

Tutto è iniziato con semplici script che venivano eseguiti sulle nostre macchine di build:

Tutto è cominciato con semplici script che venivano eseguiti sulle nostre macchine di build:

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

Tuttavia, come sappiamo, tutto si sviluppa e si complica nel tempo: eseguiamo uno script da un altro, passiamo alcuni parametri, e alla fine è stata scritta una funzione che determina a quale livello di annidamento si troviamo in bash per inserire le virgolette giuste affinché tutto funzioni.

L'evoluzione CI nel team di sviluppo mobile

Riuscite a immaginare quanto tempo richieda lo sviluppo di script del genere? Vi consiglio di non cadere in questa trappola.

Cosa si può usare al suo posto?

  • Qualsiasi linguaggio di scripting. Scrivere in Python o Kotlin Script è più comodo, perché si tratta di programmazione, non di script.
  • Oppure descrivere tutta la logica dei build sotto forma di Custom gradle tasks per il vostro progetto.

Abbiamo deciso di scegliere la seconda opzione e stiamo sistematicamente rimuovendo tutti gli script bash per scrivere molte custom gradle tasks.

Consiglio n. 2: mantenere l'infrastruttura nel codice.

È comodo quando la configurazione della Continuous Integration non è memorizzata nell'interfaccia UI di Jenkins o TeamCity, ma in file di testo direttamente nel repository del progetto. Questo garantisce la versioning. Sarà facile tornare indietro o costruire il codice su un'altra branch.

Gli script possono essere memorizzati nel progetto. E per l'ambiente?

Consiglio n. 3: Docker può aiutare con l'ambiente.

Sarà sicuramente utile per gli sviluppatori Android, ma purtroppo non per iOS.

Ecco 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

# Scarica 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

# Installa Android Build Tool e Librerie
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

Scrivendo questo file Docker (ti confido, non è 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.

Due principali argomenti per cui ha senso: scalabilità e ripetibilità. Con Docker è 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 è molto semplice, con gli emulatori è un po' più complesso: sarà necessario impegnarsi un po' (o scaricare di nuovo una versione pronta da GitHub).

Consiglio n. 4: non dimenticare che le verifiche si fanno non per il gusto di farle, ma per le persone.

Per gli sviluppatori è fondamentale avere un feedback veloce e, soprattutto, chiaro: cosa si è rotto, quale test è fallito, dove guardare il build log.

Consiglio n. 5: sii pragmatico nello sviluppo dell'integrazione continua.

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.

Consiglio n. 6: utilizza strumenti pronti all'uso.

Ora ci sono molte aziende che offrono CI in cloud.

L'evoluzione CI nel team di sviluppo mobile

Per piccoli team, è una buona soluzione. Non c'è bisogno di mantenere nulla, basta pagare un po' di soldi, assemblare la propria applicazione e persino eseguire i test di strumentazione.

Consiglio n. 7: in un grande team, le soluzioni interne sono più vantaggiose.

Ma prima o poi, con la crescita del team, le soluzioni interne diventeranno più convenienti. C'è 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ù difficile e richiede investimenti maggiori.

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.

L'evoluzione CI nel team di sviluppo mobile

È evidente che qualsiasi miglioramento diventa sempre più difficile da ottenere. Guardando questo grafico, si può 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 è un'idea poco valida. Ma non dedicarsi affatto al Continuous Integration in un grande team è anch'essa una cattiva idea, perché ci vorrà ancora più tempo per risolvere i problemi di integrazione, riparare la comunicazione, ecc.

Abbiamo iniziato con il concetto che l'automazione è necessaria, perché le persone costano, commettono errori e sono inclini alla pigrizia. Ma anche l'automazione è fatta da persone. Quindi, questi problemi riguardano anche l'automazione.

  • L'automazione è costosa. Ricordate il grafico dei costi lavorativi.
  • Nell'automazione, le persone commettono errori.
  • A volte è molto faticoso automatizzare, perché tutto funziona già. Perché migliorare ulteriormente? Perché tutto questo Continuous Integration?

Ma ho delle statistiche: nel 20% delle build vengono rilevati errori. E questo non succede perché i nostri sviluppatori scrivono codice male. Succede perché gli sviluppatori sono sicuri che, se commettono un errore, non arriverà in develop, verrà catturato dai controlli automatici. Di conseguenza, gli sviluppatori possono dedicare più tempo alla scrittura di codice e a cose interessanti, piuttosto che eseguire controlli localmente.

Impegnatevi nel Continuous Integration. Ma con moderazione.

A proposito, Nikolai Nesterov non solo fa ottime presentazioni, ma è anche membro del comitato programmatico AppsConf e aiuta gli altri a preparare interventi significativi per voi. La completezza e l'utilità del programma della prossima conferenza possono essere valutate in base agli argomenti nel calendarioE per ulteriori dettagli vi aspettiamo il 22-23 aprile all'Infospazio.

Fonte: habr.com

Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster