Come si ridimensionano i test di integrazione?


21

Sto studiando tecniche e strategie per ridimensionare il nostro numero crescente di test di integrazione sul nostro prodotto attuale, in modo che possano (umanamente) rimanere parte del nostro sviluppo e del processo di CI.

A circa 200+ test di integrazione stiamo già raggiungendo il segno 1 ora per completare una prova completa (su una macchina desktop dev), e questo sta influenzando negativamente la capacità di uno sviluppatore di tollerare l'esecuzione dell'intera suite come parte dei processi push di routine. Che sta influenzando la motivazione per essere disciplinati nel crearli bene. Test di integrazione eseguiamo solo scenari chiave dalla parte anteriore a quella posteriore e utilizziamo un ambiente che rispecchia la produzione, che viene creato da zero ogni test.

A causa del tempo necessario per l'esecuzione, sta creando un terribile ciclo di feedback e molti cicli sprecati in attesa che le macchine finiscano le prove, indipendentemente da quanto siano concentrate le prove. Non dimenticare l'impatto negativo più costoso su flusso e progresso, sanità mentale e sostenibilità.

Prevediamo di fare 10 volte più test di integrazione prima che questo prodotto inizi a rallentare (non ne ho idea, ma non sembra che stiamo ancora iniziando in termini di funzionalità). Dobbiamo aspettarci di riuscire a trovarci tra poche centinaia o un paio di migliaia di test di integrazione, credo a un certo punto.

Per essere chiari, cercare di evitare che ciò diventi una discussione sui test unitari e sui test di integrazione (che non dovrebbero mai essere scambiati). Stiamo eseguendo entrambi i test unitari con TDD E test di integrazione in questo prodotto. In effetti, eseguiamo test di integrazione ai vari livelli nell'architettura dei servizi di cui disponiamo, laddove ciò ha senso per noi, in quanto dobbiamo verificare dove introduciamo cambiamenti radicali quando cambiamo i modelli della nostra architettura nelle altre aree del sistema. 

Un po 'del nostro stack tecnologico. Attualmente stiamo testando un ambiente di emulazione (CPU e memoria intensiva) per eseguire i nostri test da un capo all'altro. Che è composto da servizi Web REST di Azure per un backend noSql (ATS). Stiamo simulando il nostro ambiente di produzione eseguendo l'Emulatore desktop di Azure + IISExpress. Siamo limitati a un emulatore e un repository di backend locale per macchina di sviluppo.

Disponiamo anche di un elemento della configurazione basato su cloud, che esegue lo stesso test nello stesso ambiente emulato e le esecuzioni del test impiegano il doppio del tempo (2 ore +) nel cloud con il nostro attuale fornitore di elementi della configurazione. Abbiamo raggiunto i limiti dello SLA dei provider di servizi di cloud cloud in termini di prestazioni hardware e abbiamo superato la loro tolleranza sul tempo di esecuzione dei test. Per essere onesti con loro, le loro specifiche non sono male, ma la metà di una macchina desktop grintosa interna chiaramente.

Stiamo usando una strategia di test per ricostruire il nostro archivio dati per ogni gruppo logico di test e precaricare i dati dei test. Assicurando in modo completo l'integrità dei dati, ciò aggiunge un impatto del 5-15% su ogni test. Quindi pensiamo che ci sia poco da guadagnare per ottimizzare questa strategia di test a questo punto nello sviluppo del prodotto. 

Il lungo e il breve è che: mentre potremmo ottimizzare il throughput di ogni test (anche se fino al 30% -50% ciascuno), non ridimensioneremo ancora efficacemente nel prossimo futuro con diverse centinaia di test. 1 ora ora è ancora molto al di là di umanamente tollerabile, abbiamo bisogno di un ordine di miglioramento di grandezza nel processo complessivo per renderlo sostenibile.

Quindi, sto studiando quali tecniche e strategie possiamo usare per ridurre drasticamente i tempi di test.

  • Scrivere meno test non è un'opzione. Per favore, non discutiamo di quello in questo thread.
  • L'uso di hardware più veloce è sicuramente un'opzione, sebbene molto costoso.
  • Anche l'esecuzione di gruppi di test / scenari su hardware separato in parrallel è sicuramente un'opzione preferita.
  • La creazione di un raggruppamento di test attorno a funzionalità e scenari in fase di sviluppo è plausibile, ma alla fine non è affidabile nel dimostrare la piena copertura o la sicurezza che il sistema non è interessato da un cambiamento. 
  • L'esecuzione in un ambiente di gestione temporanea su scala cloud invece dell'esecuzione nell'emulatore desktop è tecnicamente possibile, anche se iniziamo ad aggiungere tempi di distribuzione alle prove (~ 20 minuti ciascuna all'inizio della corsa di prova per distribuire il materiale).
  • Dividere i componenti del sistema in parti logiche indipendenti è plausibile in una certa misura, ma su questo ci aspettiamo un chilometraggio limitato, poiché le interruzioni tra i componenti dovrebbero aumentare con il tempo. (vale a dire che un cambiamento è in grado di influenzare gli altri in modi inaspettati - come spesso accade quando un sistema viene sviluppato in modo incrementale)

Volevo vedere quali strategie (e strumenti) altri stanno usando in questo spazio.

(Devo credere che altri possano vedere questo tipo di difficoltà usando determinati set tecnologici.))

[Aggiornamento: 16/12/2016: Alla fine abbiamo investito di più nei test paralleli CI, per una discussione del risultato: http://www.mindkin.co.nz/blog/2015/12/16/16-jobs]


Da quando ho scritto questo post, ho studiato che nCrunch (che usiamo ampiamente per i nostri test unitari) potrebbe essere uno strumento che può offrirci una tattica. Evidentemente ha la capacità di inviare test a macchine remote ed eseguirle in parallelo. Quindi, identificare gruppi di test di integrazione, oltre a più istanze di macchine cloud con specifiche elevate, potrebbe essere una cosa da provare? nCrunch afferma che questa è esattamente l'intenzione di questa capacità. Qualcun altro ha provato questo?
Jezz Santos,

Sembra che questo stia discendendo in una discussione su ciò che è, e cosa non è un test di integrazione, e l'incomprensione delle persone sui test unitari e sui test di integrazione, oh ragazzo!
Jezz Santos,

Risposte:


9

Ho lavorato in un luogo che ha richiesto 5 ore (su 30 macchine) per eseguire i test di integrazione. Ho rifattorizzato la base di codice e fatto invece test unitari per le nuove cose. I test unitari sono durati 30 secondi (su 1 macchina). Oh, e anche i bug sono andati giù. E i tempi di sviluppo da quando sapevamo esattamente cosa si è rotto con i test granulari.

Per farla breve, non lo fai. I test di integrazione completi crescono in modo esponenziale man mano che cresce la base di codice (più codice significa più test e più codice significa che tutti i test impiegano più tempo per essere eseguiti poiché c'è più "integrazione" per funzionare). Direi che qualsiasi cosa nell'intervallo di "ore" perde la maggior parte dei vantaggi dell'integrazione continua poiché il circuito di feedback non è presente. Anche un ordine di miglioramento della grandezza non è abbastanza per farti bene - ed è in nessun posto vicino per renderti scalabile.

Quindi consiglierei di ridurre i test di integrazione ai più ampi e vitali test del fumo. Possono quindi essere eseguiti di notte o con intervalli non continui, riducendo gran parte del bisogno di prestazioni. I test unitari, che crescono solo in modo lineare man mano che aggiungi più codice (aumentano i test, il runtime per test non lo fanno) sono la strada da percorrere per la scala.


Sono d'accordo. I test unitari sono molto più scalabili e supportano un loop di feedback più veloce.
Brandon,

8
Potresti aver perso quel punto. Il PO effettua già numerosi test Uint e test di integrazione in questione. I test unitari non sostituiscono mai i test di integrazione. Strumento diverso, pratiche diverse, scopi diversi, risultati diversi. Non è mai una questione di uno o l'altro.
Jezz Santos,

1
Aggiunta chiarezza al post per affermare chiaramente che costruiamo questo prodotto usando TDD, quindi abbiamo già migliaia di unit test, supportati dai test di integrazione in questione. .
Jezz Santos,

8

I test di integrazione dureranno sempre perché devono imitare un utente reale. Proprio per questo motivo non dovresti eseguirli tutti in modo sincrono!

Dato che stai già eseguendo roba nel cloud, mi sembra che tu sia in una posizione privilegiata per ridimensionare i tuoi test su più macchine.

In casi estremi, creare un nuovo ambiente per test ed eseguirli tutti contemporaneamente. I test di integrazione impiegheranno solo il tempo di esecuzione più lungo.


Bella idea! guardando una strategia del genere, ma con alcuni strumenti che aiutano i test distribuiti
Jezz Santos,

4

Ridurre / ottimizzare i test mi sembra l'idea migliore, ma nel caso in cui non sia un'opzione, ho un'alternativa da proporre (ma richiede la costruzione di alcuni semplici strumenti proprietari).

Ho riscontrato un problema simile ma non nei nostri test di integrazione (quelli eseguiti in pochi minuti). Invece era semplicemente nelle nostre build: base di codice C su larga scala, ci sarebbero volute ore per la costruzione.

Ciò che ho visto come estremamente dispendioso è stato il fatto che stavamo ricostruendo l' intera cosa da zero (circa 20.000 file di origine / unità di compilazione) anche se solo pochi file di origine sono cambiati, e quindi spendendo ore per un cambiamento che dovrebbe richiedere solo secondi o minuti al peggio.

Quindi abbiamo provato il collegamento incrementale sui nostri server di build, ma non era affidabile. A volte darebbe falsi negativi e non si baserebbe su alcuni commit, solo per poi riuscire su una ricostruzione completa. Peggio ancora, a volte darebbe falsi positivi e riferire un successo di build, solo per lo sviluppatore di unire una build rotta nel ramo principale. Quindi siamo tornati a ricostruire tutto ogni volta che uno sviluppatore ha inviato modifiche dal suo ramo privato.

L'ho odiato così tanto. Camminavo nelle sale conferenze con metà degli sviluppatori che giocavano ai videogiochi e semplicemente perché c'era poco altro da fare durante l'attesa di build. Ho cercato di ottenere un vantaggio in termini di produttività tramite il multitasking e l'avvio di una nuova filiale una volta che mi sono impegnato in modo da poter lavorare sul codice durante l'attesa delle build, ma quando un test o una build hanno avuto esito negativo, è diventato troppo doloroso mettere in coda le modifiche oltre quel punto e prova a sistemare tutto e ricama tutto indietro.

Side Project Waiting, Integrate Later

Quindi quello che ho fatto invece è stato quello di creare un framework scheletrico dell'applicazione, lo stesso tipo di interfaccia utente di base e parti rilevanti dell'SDK che avrei dovuto sviluppare come un progetto completamente separato. Quindi scriverei un codice indipendente contro quello mentre aspetto build, al di fuori del progetto principale. Ciò mi ha dato almeno un po 'di codice per fare in modo che potessi rimanere un po' produttivo, e poi avrei iniziato a integrare quel lavoro fatto completamente al di fuori del prodotto nel progetto in seguito - frammenti di codice laterali. Questa è una strategia per i tuoi sviluppatori se si trovano ad aspettare molto.

Analisi manuale dei file di origine per capire cosa ricostruire / rieseguire

Eppure odiavo il modo in cui sprecavamo così tanto tempo per ricostruire tutto tutto il tempo. Quindi mi sono impegnato per un paio di fine settimana a scrivere del codice che scansionasse effettivamente i file per le modifiche e ricostruisse solo i progetti pertinenti - ancora una ricostruzione completa, nessun collegamento incrementale, ma solo dei progetti che devono essere ricostruiti ( i cui file dipendenti, analizzati in modo ricorsivo, sono cambiati). Questo è stato totalmente affidabile e dopo averlo dimostrato e testato in modo esauriente, siamo stati in grado di utilizzare quella soluzione. Ciò ha ridotto i tempi di costruzione medi da ore a pochi minuti poiché stavamo solo ricostruendo i progetti necessari (sebbene le modifiche all'SDK centrale potessero richiedere ancora un'ora, ma lo abbiamo fatto molto meno frequentemente delle modifiche localizzate).

La stessa strategia dovrebbe essere applicabile ai test di integrazione. Basta analizzare ricorsivamente i file di origine per capire da quali file dipendono i test di integrazione (ad es. importIn Java,#includein C o C ++) sul lato server e i file inclusi / importati da tali file e così via, creando un grafico completo del file di dipendenza include / import per il sistema. A differenza dell'analisi della build che forma un DAG, il grafico dovrebbe essere non indirizzato poiché è interessato a qualsiasi file modificato che contiene codice che potrebbe essere eseguito indirettamente *. Rieseguire il test di integrazione solo se uno di quei file nel grafico per il test di integrazione di interesse è cambiato. Anche per milioni di righe di codice, è stato facile eseguire l'analisi in meno di un minuto. Se hai file diversi dal codice sorgente che possono influire su un test di integrazione, come i file di contenuto, forse puoi scrivere metadati in un commento nel codice sorgente indicando tali dipendenze nei test di integrazione, in modo che se tali file esterni dovessero cambiare, i test anche riesegui.

* Ad esempio, se test.c include foo.h, anch'esso incluso da foo.c, una modifica a test.c, foo.h o foo.c dovrebbe contrassegnare il test integrato come un nuovo ciclo.

Questo può richiedere un'intera giornata o due per programmare e testare, specialmente nell'ambiente formale, ma penso che dovrebbe funzionare anche per i test di integrazione e ne vale la pena se non hai altra scelta che aspettare nell'intervallo di ore per le build per terminare (a causa della costruzione o dei test o del processo di confezionamento o altro). Ciò può tradursi in così tanti manhour persi nel giro di pochi mesi che ridurrebbe il tempo necessario per costruire questo tipo di soluzione proprietaria, nonché uccidere l'energia della squadra e aumentare lo stress causato dai conflitti nelle fusioni più grandi, fatto di meno spesso a causa di tutto il tempo sprecato nell'attesa. È solo un male per la squadra nel suo insieme quando trascorrono gran parte del loro tempo ad aspettare cose.tutto da ricostruire / rieseguire / riconfezionare ad ogni piccola modifica.


3

Sembra che tu abbia troppi test di integrazione. Richiama test piramide . I test di integrazione appartengono al centro.

Come esempio prendere un repository con metodo set(key,object), get(key). Questo repository è ampiamente utilizzato in tutta la tua base di codice. Tutti i metodi che dipendono da questo repository verranno testati con un repository falso. Ora hai solo bisogno di due test di integrazione, uno per set e uno per get.

Alcuni di questi test di integrazione potrebbero probabilmente essere convertiti in test unitari. Ad esempio, a mio avviso i test end-to-end dovrebbero solo verificare che il sito sia configurato correttamente con la stringa di connessione corretta e i domini corretti.

I test di integrazione devono verificare che l'ORM, i repository e le astrazioni della coda siano corretti. Come regola generale, non è necessario alcun codice di dominio per i test di integrazione - solo astrazioni.

Quasi tutto il resto può essere testato in unità con implementazioni stubbed / mocked / faked / in-mem per dipendenze.


1
Prospettiva interessante. I nostri test di integrazione non stanno provando a verificare ogni permutazione di ogni parametro di ogni chiamata ReST. A nostro avviso, questo non è un test di integrazione. Stanno eseguendo scenari chiave end-to-end attraverso l'API che a sua volta ha colpito vari negozi back-end e altri sistemi. Lo scopo è quello di garantire che, come le API cambiano, identificano quali scenari richiedono attenzione (ovvero non funzionano più come previsto).
Jezz Santos,

1
Abbiamo test di integrazione a vari livelli nell'architettura. Nel tuo esempio, abbiamo test unitari per le classi che accedono all'archivio dati, quindi sappiamo che effettuano le chiamate giuste al nostro archivio dati, abbiamo test di integrazione per configurare una copia dei nostri archivi e testare che leggono e scrivono i dati correttamente con il negozio. Quindi usiamo quelle classi di dati in un'API REST, che creiamo con unit test, e quindi test di integrazione che avviano il servizio Web e chiamano per assicurarsi che i dati arrivino da capo a fronte e viceversa. Stai suggerendo di fare troppi test qui?
Jezz Santos,

Ho aggiornato la mia risposta come risposta ai tuoi commenti.
Esben Skov Pedersen,

2

Nella mia esperienza in un ambiente Agile o DevOps in cui sono comuni condutture a erogazione continua, i test di integrazione devono essere eseguiti man mano che ciascun modulo viene completato o adattato. Ad esempio, in molti ambienti di pipeline a consegna continua, non è raro avere più distribuzioni di codice per sviluppatore al giorno. L'esecuzione di una serie rapida di test di integrazione al termine di ogni fase di sviluppo prima della distribuzione dovrebbe essere una pratica standard in questo tipo di ambiente. Per ulteriori informazioni, un ottimo eBook da includere nella lettura su questo argomento è una guida pratica al test in DevOps , scritta da Katrina Clokie.

Per testare in modo efficiente in questo modo, il nuovo componente deve essere testato rispetto ai moduli completati esistenti in un ambiente di test dedicato o contro Stub e Driver. A seconda delle esigenze, è generalmente una buona idea conservare una libreria di Stub e Driver per ciascun modulo dell'applicazione in una cartella o libreria per consentire un uso ripetitivo e ripetitivo dei test di integrazione. Mantenere gli Stub e i Driver organizzati in questo modo semplifica l'esecuzione di modifiche iterative, mantenendole aggiornate e con prestazioni ottimali per soddisfare le esigenze di test in corso.

Un'altra opzione da considerare è una soluzione sviluppata originariamente intorno al 2002, denominata Service Virtualization. Ciò crea un ambiente virtuale, simulando l'interazione del modulo con le risorse esistenti a scopo di test in un DevOps aziendale complesso o nell'ambiente Agile.

Questo articolo può essere utile per comprendere meglio come eseguire i test di integrazione nell'azienda


Mentre questo può funzionare (se il sistema può essere suddiviso in tali moduli, ma non tutti i prodotti possono farlo), una volta era la norma qualche tempo fa, ritardava effettivamente l'integrazione, perdendo così tutti i vantaggi di CI / CD. Un po 'contro-agile, non credi? I problemi rilevati in tali test di integrazione non possono essere facilmente e rapidamente abbinati a un particolare commit, quindi richiedono indagini complete, da zero, proprio come i bug che arrivano dalla produzione (e sai quanto sono più costosi da risolvere).
Dan Cornilescu,

1

Hai misurato ogni test per vedere dove viene impiegato il tempo? E poi, misurato le prestazioni del codebase se c'è un bit particolarmente lento. Il problema generale è uno dei test o la distribuzione o entrambi?

In genere si desidera ridurre l'impatto del test di integrazione in modo da minimizzarne l'esecuzione su modifiche relativamente minori. Quindi puoi lasciare il test completo per un'esecuzione "QA" che esegui quando il ramo viene promosso al livello successivo. Quindi hai test unitari per i rami di sviluppo, esegui test di integrazione ridotti quando uniti ed esegui un test di integrazione completo quando unito a un ramo candidato al rilascio.

Ciò significa che non è necessario ricostruire, reimballare e ridistribuire tutto ad ogni commit. È possibile organizzare la propria configurazione, nell'ambiente di sviluppo, per eseguire una distribuzione il più economica possibile fidandosi che sia OK. Invece di eseguire il rollup di un'intera VM e distribuire l'intero prodotto, lasciare la VM con la versione precedente in posizione e copiare i nuovi file binari, ad esempio (YMMV a seconda di ciò che si deve fare).

Questo approccio complessivamente ottimistico richiede ancora il test completo, ma che può essere eseguito in una fase successiva quando il tempo impiegato è meno urgente. (ad es. puoi eseguire il test completo una volta di notte, se ci sono problemi lo sviluppatore può risolverli al mattino). Ciò ha anche il vantaggio di aggiornare il prodotto sulla piattaforma di integrazione per i test del giorno successivo: potrebbe non essere aggiornato poiché gli sviluppatori cambiano le cose, ma solo di 1 giorno.

Abbiamo riscontrato un problema simile nell'esecuzione di uno strumento di analisi statica basato sulla sicurezza. Le esecuzioni complete richiederebbero anni, quindi ci siamo trasferiti eseguendolo dai commit degli sviluppatori a un commit di integrazione (ovvero avevamo un sistema in cui gli sviluppatori dicevano che erano finiti, è stato unito a un ramo di "livello 2" dove sono stati eseguiti più test, incluso perf test. Quando è stato completato, è stato unito a un ramo QA per la distribuzione. L'idea è quella di rimuovere le corse regolari che si verificherebbero continuamente su corse effettuate di notte - gli sviluppatori otterrebbero i risultati al mattino e non influenzerebbero il loro sviluppo concentrarsi fino a dopo nel loro ciclo di sviluppo).


1

Ad un certo punto, il completamento di una serie completa di test di integrazione può richiedere molte ore, anche su hardware costoso. Una delle opzioni non è quella di eseguire la maggior parte di questi test su ogni commit e di eseguirli ogni notte o in modalità batch continua (una volta per commit multipli).

Ciò, tuttavia, crea un nuovo problema: gli sviluppatori non ricevono feedback immediati e le build interrotte potrebbero passare inosservate. Per risolvere questo problema, è importante che sappiano che qualcosa è rotto in ogni momento. Costruire strumenti di notifica come Catlight o il notificatore del vassoio di TeamCity può essere abbastanza utile.

Ma ci sarà ancora un altro problema. Anche quando lo sviluppatore vede che la build è rotta, potrebbe non correre a controllarla. Dopotutto, qualcun altro potrebbe già controllarlo, giusto?

Per questo motivo, questi due strumenti hanno una funzione di "indagine di costruzione". Indica se qualcuno del team di sviluppo sta effettivamente controllando e risolvendo la build non funzionante. Gli sviluppatori possono fare volontariato per verificare la build e, fino a quando ciò accade, tutti i membri del team saranno infastiditi da un'icona rossa vicino all'orologio.


0

Sembra che la tua base di codice stia crescendo, e un po 'di gestione del codice aiuterà. Usiamo Java, quindi mi scuso in anticipo se presumo questo.

  • Un grande progetto deve essere suddiviso in singoli progetti più piccoli che vengono compilati in librerie. Strumenti Java come Nexus lo rendono facile.
  • Ogni libreria dovrebbe implementare un'interfaccia. Questo aiuta a eliminare la biblioteca in test di livello superiore. Ciò è particolarmente utile se la libreria accede a un database o a un archivio dati esterno (ad esempio un mainframe). In tali casi, portare i dati del mainframe o del database in uno stato ripetibile sarà probabilmente lento e potrebbe essere impossibile.
  • I test di integrazione per ciascuna libreria possono essere completi, ma devono essere eseguiti solo quando viene impegnata la nuova sorgente della libreria.
  • I test di integrazione di livello superiore dovrebbero semplicemente chiamare le librerie e presumere che siano perfette.

Il negozio Java in cui lavoro utilizza questo approccio e raramente siamo bloccati in attesa dell'esecuzione dei test di integrazione.


Grazie, ma penso che non abbiamo la stessa comprensione dello scopo e dell'applicazione dei test di integrazione in questo contesto. È possibile che si stiano combinando i test di integrazione con i test unitari.
Jezz Santos,

0

Un altro possibile approccio da mantenere nei test di integrazione della pipeline CI (o qualsiasi tipo di verifica, inclusi build) con tempi di esecuzione lunghi o che richiedono risorse limitate e / o costose è quello di passare dai sistemi di CI tradizionali basati su verifiche post-commit (che sono suscettibile alla congestione ) a uno basato su verifiche preliminari .

Invece di impegnare direttamente le loro modifiche nelle filiali, gli sviluppatori le sottopongono a un sistema di verifica automatizzato centralizzato che esegue le verifiche e:

  • in caso di successo, commette automaticamente le modifiche nel ramo
  • in caso di esito negativo, notifica ai rispettivi mittenti di rivalutare le loro modifiche

Tale approccio consente di combinare e testare insieme più modifiche inviate, aumentando potenzialmente la velocità di verifica CI effettiva molte volte.

Un esempio è il sistema di gating basato su Gerrit / Zuul utilizzato da OpenStack .

Un altro è ApartCI ( disclaimer - io sono il suo creatore e il fondatore dell'azienda che lo offre).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.