È buona norma eseguire unit test negli hook di controllo della versione?


43

Dal punto di vista tecnico è possibile aggiungere alcuni hook di pre / post push che eseguiranno test unitari prima di consentire l'unione di alcuni commit specifici al ramo predefinito remoto.

La mia domanda è: è meglio mantenere i test unitari nella pipeline di build (quindi, introducendo commit rotti in repo) o è meglio non permettere che si verifichino commit "cattivi".

Mi rendo conto di non essere limitato con queste due opzioni. Ad esempio, posso consentire a tutti i commit di essere ramificati e test prima di inviare il merge commit al repository. Ma se devi scegliere esattamente tra queste due soluzioni, quale sceglierai e per quali motivi esatti?


Risposte:


35

No, non lo è, per due motivi:

Velocità

Gli impegni dovrebbero essere veloci. Un commit che richiede 500 ms., Ad esempio, è troppo lento e incoraggerà gli sviluppatori a impegnarsi con più parsimonia. Dato che su qualsiasi progetto più grande di un Hello World, avrai dozzine o centinaia di test, ci vorrà troppo tempo per eseguirli durante il pre-commit.

Naturalmente, le cose peggiorano per i progetti più grandi con migliaia di test che vengono eseguiti per minuti su un'architettura distribuita o settimane o mesi su una singola macchina.

La parte peggiore è che non c'è molto che puoi fare per renderlo più veloce. Piccoli progetti Python che hanno, diciamo, cento test unitari, richiedono almeno un secondo per essere eseguiti su un server medio, ma spesso molto più a lungo. Per un'applicazione C #, avrà una media di quattro-cinque secondi, a causa del tempo di compilazione.

Da quel momento, puoi pagare $ 10.000 in più per un server migliore che ridurrà il tempo, ma non di molto, oppure eseguire test su più server, che rallenteranno le cose.

Entrambi pagano bene quando si hanno migliaia di test (oltre a test funzionali, di sistema e di integrazione), che consentono di eseguirli in pochi minuti anziché settimane, ma questo non ti aiuterà per progetti su piccola scala.

Quello che puoi fare, invece, è:

  • Incoraggia gli sviluppatori a eseguire test fortemente correlati al codice che hanno modificato localmente prima di eseguire un commit. Probabilmente non possono eseguire migliaia di unit test, ma possono eseguirne cinque e dieci.

    Assicurati che trovare test pertinenti ed eseguirli sia effettivamente facile (e veloce). Visual Studio, ad esempio, è in grado di rilevare quali test potrebbero essere interessati dalle modifiche apportate dall'ultima esecuzione. Altri IDE / piattaforme / lingue / framework potrebbero avere funzionalità simili.

  • Mantieni il commit il più velocemente possibile. Far rispettare le regole di stile è OK, perché spesso è l'unico posto dove farlo e perché tali controlli sono spesso sorprendentemente veloci. Fare analisi statiche va bene non appena si accelera, cosa che accade raramente. L'esecuzione dei test unitari non è corretta.

  • Eseguire test unitari sul server di integrazione continua.

  • Assicurati che gli sviluppatori siano informati automaticamente quando interrompono la compilazione (o quando i test unitari falliscono, il che è praticamente la stessa cosa se consideri un compilatore come uno strumento che controlla alcuni dei possibili errori che puoi introdurre nel tuo codice).

    Ad esempio, andare su una pagina Web per verificare gli ultimi build non è una soluzione. Dovrebbero essere informati automaticamente . Mostrare un popup o inviare un SMS sono due esempi di come possono essere informati.

  • Assicurati che gli sviluppatori comprendano che interrompere la build (o fallire i test di regressione) non va bene e che non appena accade, la loro priorità principale è risolverlo. Non importa se stanno lavorando a una funzionalità ad alta priorità che il loro capo ha chiesto di spedire per domani: hanno fallito la costruzione, dovrebbero ripararla.

Sicurezza

Il server che ospita il repository non deve eseguire codice personalizzato, ad esempio test di unità, soprattutto per motivi di sicurezza. Quelle ragioni erano già state spiegate nel corridore CI sullo stesso server di GitLab?

Se, d'altra parte, la tua idea è quella di avviare un processo sul server di build dall'hook pre-commit, allora rallenterà ulteriormente i commit.


4
Sono d'accordo, questo è lo scopo di un server di build. Il controllo del codice sorgente è per la gestione del codice sorgente, non per garantire che l'applicazione funzioni.
Matteo

4
In casi estremi, la ritorsione potrebbe essere uno strumento di notifica necessario quando si rompe la build.

37
Mezzo secondo è troppo lento? Questa è una goccia nel secchio rispetto a dare un'ultima occhiata a ciò che viene commesso e quindi pensare e digitare un commento di commit appropriato.
Doval,

5
@Doval: la differenza è che quando dai un ultimo sguardo o pensi al commento, sei proattivo e quindi non stai aspettando. Non è il tempo che passi prima di digitare l'ultimo carattere nel tuo IDE e il tempo in cui puoi ricominciare a scrivere una volta terminato il commit che conta, ma quanto aspetti. Ecco perché i compilatori dovrebbero essere veloci; non importa che passi molto più tempo a leggere e scrivere il codice, perché quando lo fai, non stai aspettando, mentre quando il codice viene compilato, sei tentato di passare a un'altra attività.
Arseni Mourzenko,

10
@Thomas: non si tratta di distrazione, ma di fastidio. Allo stesso modo, 100 ms. "ha un impatto misurabile" su come le persone utilizzano un sito Web. Stesso modello qui: 100 ms. non è niente in confronto al tempo che passi a guardare un video di YouTube o ad avviare il tuo PC: consapevolmente , non noterai la differenza tra i 600 ms. e 700 ms. ritardo. Ma inconsciamente, ha un'influenza sul tuo comportamento. Allo stesso modo, un po 'più lentamente ti scoraggia dal commettere presto e frequentemente.
Arseni Mourzenko,

41

Vorrei essere io a non essere d'accordo con i miei compagni di risposta.

Questo è noto come Gated Check-in nel mondo TFS e mi aspetto altrove. Quando si tenta di effettuare il check-in in una succursale con il check-in gated, lo shelfet viene inviato al server, il che assicura che le modifiche vengano compilate e che i test di unità specificati (leggi: tutti) vengano superati. Se non lo fanno, ti avvisa che sei una cattiva scimmia che ha rotto la build. Se lo fanno, le modifiche vanno al controllo del codice sorgente (yay!).

In base alla mia esperienza, i check-in con gate sono uno dei processi più importanti per test unitari riusciti e, per estensione, la qualità del software.

Perché?

  • Perché i check-in controllati costringono le persone a riparare i test rotti. Non appena i test rotti diventano qualcosa che la gente può fare piuttosto che devono fare, diventano de-priorità da ingegneri pigri e / o uomini d'affari invadenti.
    • Più a lungo viene interrotto un test, più difficile (e più costoso) è risolvere.
  • Perché non appena la gente dovrebbe eseguire i test, piuttosto che devono eseguire i test, eseguire i test sono aggirata dai tecnici pigri / smemorati e / o uomini d'affari invadenti.
  • Perché non appena i test unitari incidono sul tempo di commit, le persone iniziano davvero a preoccuparsi di effettuare i test unitari . La velocità conta. La riproducibilità conta. L'affidabilità è importante. L'isolamento conta.

E, naturalmente, c'è il vantaggio che hai inizialmente citato: quando hai check-in e una solida serie di test, ogni singolo changeset è "stabile". Hai salvato tutto quel sovraccarico (e potenziale errore) di "quando è stata l'ultima buona build?" - tutte le build sono abbastanza buone da svilupparsi contro.

Sì, ci vuole tempo per compilare ed eseguire i test. Nella mia esperienza 5-10 minuti per un'app C # di buone dimensioni e ~ 5k unit test. E non mi interessa. Sì, le persone dovrebbero effettuare il check-in frequentemente. Ma dovrebbero anche aggiornare le loro attività frequentemente, o controllare la loro e-mail, o prendere del caffè o dozzine di altre cose "non funzionanti sul codice" che fanno occupare quel tempo a un ingegnere del software. Controllare il codice errato è molto più costoso di 5-10 minuti.


3
+1 Voglio aggiungere che molti progetti open source hanno una chiara distinzione tra contributo e impegno. Le ragioni sono molto simili al motivo per cui esistono check-in recintati.
JensG,

Un problema significativo con il check-in gated è che inibisce la risoluzione collaborativa di problemi di codice difficile. Ciò è ancora più significativo con i team distribuiti.
CuriousRabbit

2
@CuriousRabbit - come pensi? Le persone raramente si impegnano in modo collaborativo anche se lavorano in modo collaborativo. Altrimenti scaffali o rami di attività non datati funzionano bene per questo senza ostacolare il resto della squadra.
Telastyn,

@ Telastyn– Shelvesets è un nuovo termine per me. Mi rendo conto che questa è un'opzione MS VS, che non è un'opzione per un vasto numero di progetti. Nel campo dello sviluppo di app mobili, VS non è un giocatore.
CuriousRabbit

3
@CuriousRabbit - davvero? Una squadra distribuita nell'arco di 17 ore si preoccupa più dell'attesa di 10 minuti per un commit che della possibilità di un build rotto mentre la parte offensiva sta dormendo? Sembra ... Meno che ottimale.
Telastyn,

40

Gli commit dovrebbero essere veloci. Quando commetto un po 'di codice, voglio che venga inviato al server. Non voglio aspettare alcuni minuti mentre esegue una batteria di test. Sono responsabile di ciò che invio al server e non ho bisogno di nessuno che mi faccia da baby-sitter con hook hook.

Detto questo, una volta che arriva al server, dovrebbe essere analizzato, testato e costruito immediatamente (o in un breve periodo di tempo). Questo mi avviserebbe del fatto che i test unitari sono stati interrotti, o che non sono stati compilati, o che ho reso un casino mostrato dagli strumenti di analisi statica disponibili. Più veloce è questo (la costruzione e l'analisi), più veloce è il mio feedback e più veloce sono in grado di risolverlo (i pensieri non sono completamente cambiati dal mio cervello).

Quindi no, non mettere test e cose simili nei commit hook sul client. Se è necessario, inseriscili sul server in un post commit (perché non disponi di un server CI) o sul server di build CI e avvisami in modo appropriato dei problemi con il codice. Ma non impedire che il commit avvenga in primo luogo.

Vorrei anche sottolineare che con alcune interpretazioni di Test Driven Development, si dovrebbe verificare un test unitario che si rompe per primo . Ciò dimostra e documenta la presenza del bug. Quindi un check-in successivo sarebbe il codice che corregge il test unitario. Prevenire eventuali check-in fino al superamento dei test unitari ridurrebbe il valore effettivo del controllo in un test unitario che non documenta il problema.

Correlati: dovrei avere test unitari per difetti noti? e qual è il valore del controllo nei test unitari falliti?


2
Commits should run fast.quali sono i vantaggi di questo? Sono curioso perché attualmente utilizziamo check-in gated. Di solito il mio check-in è un cumulo di circa un'ora di lavoro, quindi un'attesa di 5 minuti non è un grosso problema. In realtà ho trovato che i suoi normalmente i momenti in cui mi sono di fretta che l'accumulo di convalida è più utile per la cattura di errori stupidi (come risultato di correre)
Justin

1
@Justin Un'attesa di cinque minuti è un'attesa di cinque minuti, non importa dove sia. Non si dovrebbe aver bisogno di rompere le spade nerf ogni volta che si esegue un commit. E non è insolito per me spaccare circa un'ora di lavoro in più commit che sono unità concettuali l'uno dell'altro - "commit del codice di servizio restante" seguito da "commit del codice per la pagina client servita" come due commit separati (per non parlare del css tweak come altro commit). Se ciascuno di questi impiegava 5 minuti a funzionare, 1/6 del mio tempo è trascorso in attesa di test. Ciò porterebbe a un maggior numero di commit di rollup che sono più difficili da rintracciare i bug.

5 minuti di attesa per eseguire i test unitari? Sento che i progetti a cui state lavorando dovrebbero essere suddivisi in componenti più piccoli.
user441521

@Justin catching silly mistakes (as a result of rushing)esattamente. correre in generale è una cattiva pratica nell'ingegneria del software. Robert C. Martin consiglia di scrivere codice come fare un intervento su youtube.com/watch?v=p0O1VVqRSK0
Jerry Joseph,

10

In linea di principio, penso che abbia senso impedire alle persone di apportare modifiche alla linea principale che rompono la costruzione. Cioè, il processo per apportare modifiche al ramo principale del repository dovrebbe richiedere che tutti i test continuino a passare. La rottura della build è semplicemente troppo costosa in termini di tempo perso per tutti gli ingegneri del progetto per fare qualsiasi altra cosa.

Tuttavia, la particolare soluzione degli hook hook non è un buon piano.

  1. Lo sviluppatore deve attendere l'esecuzione dei test durante il commit. Se lo sviluppatore deve attendere sulla sua workstation che tutti i test vengano superati, hai perso tempo prezioso per l'ingegnere. L'ingegnere deve essere in grado di passare al compito successivo, anche se dovrà tornare indietro perché i test hanno fallito.
  2. Gli sviluppatori potrebbero voler impegnare il codice non funzionante in un ramo. In un'attività più ampia, la versione del codice degli sviluppatori potrebbe impiegare molto tempo a non passare. Ovviamente, fondere quel codice nella linea principale sarebbe molto male. Ma è piuttosto importante che lo sviluppatore possa ancora utilizzare il controllo versione per tenere traccia dei suoi progressi.
  3. Ci sono buoni motivi occasionali per saltare il processo e bypassare i test.

2
Il numero 1 viene eliminato consentendo agli sviluppatori di effettuare il check-in in una filiale personale o in un repository locale. È solo quando uno sviluppatore desidera il proprio codice da qualche parte che altri sviluppatori possono vederlo che devono essere eseguiti i test unitari. Come per il numero 1, il numero 2 è ovviato solo dai ganci ai rami della linea principale. # 3 è ovviato dal fatto che A) Qualsiasi funzione di questo tipo può essere disabilitata, anche se è una seccatura (e dovrebbe essere una seccatura) e B) I singoli test unitari falliti possono essere disabilitati.
Brian,

@Brian, sono totalmente d'accordo, puoi farlo funzionare. Ma, provare a farlo bloccando il lato hook del server di commit non funzionerà.
Winston Ewert,

punti buoni. Breaking the build is simply too costly in terms of lost time for all engineers on the projectSuggerirei di utilizzare una sorta di strumento di notifica di build per evitare che tutti gli ingegneri finiscano per perdere tempo su ogni build interrotta
Jerry Joseph,

3

No, non dovresti, come hanno indicato altre risposte.

Se si desidera disporre di una base di codice che garantisca di non avere esito negativo nei test, è possibile invece sviluppare sui rami delle funzionalità ed eseguire richieste pull nel master. Quindi è possibile definire i presupposti per l'accettazione di tali richieste pull. Il vantaggio è che puoi spingere molto velocemente e i test vengono eseguiti in background.


2

Dover aspettare il successo della compilazione e dei test su ogni commit nel ramo principale è davvero terribile, penso che tutti siano d'accordo su questo.

Ma ci sono altri modi per ottenere un ramo principale coerente. Ecco un suggerimento, un po 'simile nella vena dei check-in gated in TFS, ma che è generalizzabile a qualsiasi sistema di controllo di versione con rami, anche se userò principalmente termini git:

  • Avere un ramo di gestione temporanea in cui è possibile eseguire il commit delle fusioni solo tra i rami di sviluppo e il ramo principale

  • Configurare un hook che avvia o mette in coda una build e verifica i commit eseguiti sul ramo di gestione temporanea, ma che non fanno attendere il committer

  • Sulla costruzione e test di successo, rende automaticamente il ramo principale andare avanti se è aggiornata

    Nota: non unire automaticamente nel ramo principale, poiché l'unione testata, se non una fusione in avanti dal punto di vista del ramo principale, potrebbe non riuscire se unita al ramo principale con commit tra

Come conseguenza:

  • Vietare l'essere umano si impegna nella filiale principale, automaticamente se è possibile, ma anche come parte del processo ufficiale se c'è una scappatoia o se non è tecnicamente possibile far valere questo

    Almeno, puoi essere sicuro che nessuno lo farà involontariamente, o senza malizia, una volta che è una regola di base. Non dovresti mai tentare di farlo.

  • Dovrai scegliere tra:

    • Un singolo ramo di gestione temporanea, che renderà effettivamente fallite le fusioni altrimenti riuscite se fallisce una fusione precedente ma non compilata e non testata

      Almeno, saprai quale unione non è riuscita e qualcuno lo correggerà, ma le fusioni tra di loro non sono banalmente rintracciabili (dal sistema di controllo della versione) per i risultati di ulteriori build e test.

      Puoi osservare l'annotazione (o la colpa) del file, ma a volte una modifica in un file (ad es. Configurazione) genererà errori in luoghi imprevisti. Tuttavia, questo è un evento piuttosto raro.

    • Più rami di gestione temporanea, che consentiranno fusioni riuscite senza conflitti per raggiungere il ramo principale

      Anche nel caso in cui un altro ramo di gestione temporanea presenta fusioni non riuscite non in conflitto . La tracciabilità è un po 'migliore, almeno nel caso in cui una fusione non si aspettasse un cambiamento che influisca da un'altra fusione. Ma ancora una volta, questo è abbastanza raro da non preoccuparsi ogni giorno o ogni settimana.

      Per avere fusioni non conflittuali per la maggior parte del tempo, è importante dividere i rami di stadiazione in modo ragionevole, ad esempio per squadra, per livello o per componente / progetto / sistema / soluzione (anche se lo si chiama).

      Se nel frattempo la filiale principale è stata inoltrata a un'altra unione, è necessario eseguire nuovamente l'unione. Speriamo che questo non sia un problema con fusioni non conflittuali o con pochissimi conflitti.

Rispetto ai check-in con cancello, il vantaggio qui è che hai la garanzia di un ramo principale funzionante, perché al ramo principale è consentito solo di andare avanti, non di unire automaticamente le tue modifiche con tutto ciò che è stato commesso. Quindi il terzo punto è la differenza essenziale.


Ma il punto di avere un ramo funzionante non è (di solito) per garantire una versione stabile, ma per ridurre il thrashing causato dagli sviluppatori che lavorano insieme nello stesso codice.
Telastyn,

Non se hai un enorme repo con grandi team distribuiti a livello globale. Ad esempio, Microsoft utilizza un approccio simile e più stratificato con Windows.
acelent,

2

Preferisco che i "test unitari superati" siano un gate per l'invio del codice. Tuttavia, per farlo funzionare, avrai bisogno di alcune cose.

Avrai bisogno di un framework di build che memorizzi nella cache manufatti.

Sarà necessario un framework di test che memorizzi nella cache lo stato del test da esecuzioni di test (riuscite), con qualsiasi artefatto (i) dato.

In questo modo, i check-in con superamento dei test unitari saranno rapidi (cross-check dalla sorgente al manufatto che è stato costruito quando lo sviluppatore ha verificato i test prima del check-in), quelli con test unitari falliti verranno bloccati e gli sviluppatori che dimenticarsi convenientemente di controllare le build prima di eseguire il commit sarà incoraggiato a ricordare di farlo la prossima volta, perché il ciclo di compilazione e test è lungo.


1

Direi che dipende dal progetto e dall'ambito dei test automatizzati eseguiti sul "commit".

Se i test che ti piace eseguire nel trigger del check-in sono molto veloci, o se il flusso di lavoro degli sviluppatori forzerà comunque un po 'di lavoro amministrativo dopo tale check-in, penso che non dovrebbe importare molto e costringere gli sviluppatori a controllare solo in roba che esegue assolutamente i test più elementari. (Suppongo che eseguiresti solo i test più elementari su un tale trigger.)

E penso, velocità / flusso di lavoro permettendo, è una buona cosa non spingere le modifiche ad altri sviluppatori che falliscono i test - e sai solo se falliscono se li esegui.

Scrivi "commetti ... a ramo remoto" nella domanda, il che implicherebbe per me questo non è (a) non qualcosa che fa uno sviluppatore ogni pochi minuti, quindi una piccola attesa potrebbe essere molto accettabile, e (b) che dopo un tale impegno le modifiche al codice potrebbero influire su altri sviluppatori, quindi potrebbero essere necessari ulteriori controlli.

Posso essere d'accordo con le altre risposte su "non fare in modo che i tuoi sviluppatori si agitino i pollici mentre aspettano" tale operazione.


1

I commit rotti non dovrebbero essere consentiti sul trunk , poiché il trunk è ciò che potrebbe andare in produzione. Quindi è necessario assicurarsi che ci sia un gateway che devono passare prima di essere sul trunk . Tuttavia, i commit rotti possono andare benissimo in un repository purché non si trovi sul trunk.

D'altro canto, richiedere agli sviluppatori di attendere / risolvere i problemi prima di inviare modifiche al repository presenta una serie di inconvenienti.

Qualche esempio:

  • In TDD, è comune eseguire il commit e il push dei test non riusciti per le nuove funzionalità prima di iniziare a implementare la funzionalità
  • Lo stesso vale per la segnalazione di bug commettendo e spingendo un test fallito
  • La pressione di un codice incompleto consente a 2 o più persone di lavorare facilmente su una funzione in parallelo
  • La tua infrastruttura CI può richiedere del tempo per la verifica, ma lo sviluppatore non deve attendere
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.