Perché i test unitari non vengono visti come negativi?


93

In alcune organizzazioni, a quanto pare, parte del processo di rilascio del software consiste nell'utilizzare i test unitari, ma in qualsiasi momento nel tempo tutti i test unitari devono superare. Ad esempio, potrebbe esserci qualche schermata che mostra tutti i test unitari che passano in verde, il che dovrebbe essere buono.

Personalmente, penso che questo non sia come dovrebbe essere per i seguenti motivi:

  1. Promuove l'idea che il codice dovrebbe essere perfetto e che non dovrebbero esistere bug - che nel mondo reale è sicuramente impossibile per un programma di qualsiasi dimensione.

  2. È disincentivo pensare a unit test che falliranno. O sicuramente escogitare test unitari che sarebbero difficili da risolvere.

  3. Se in qualsiasi momento tutti i test unitari superano, allora non esiste un quadro generale dello stato del software in nessun momento. Non esiste una tabella di marcia / obiettivo.

  4. Rileva i test delle unità di scrittura in anticipo - prima dell'implementazione.

Vorrei anche suggerire che anche il rilascio di software con test unitari non riusciti non è male. Almeno allora sai che alcuni aspetti del software hanno dei limiti.

Mi sto perdendo qualcosa qui? Perché le organizzazioni prevedono che tutti i test unitari vengano superati? Non è vivere in un mondo da sogno? E in realtà non scoraggia una vera comprensione del codice?


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

Risposte:


270

Questa domanda contiene diversi malintesi su IMHO, ma il principale su cui vorrei concentrarmi è che non distingue tra rami di sviluppo locale, trunk, staging o rami di rilascio.

In un ramo di sviluppo locale, è probabile che si verifichino alcuni test unitari non funzionanti in qualsiasi momento. Nel bagagliaio, è accettabile solo in una certa misura, ma già un indicatore forte per risolvere le cose al più presto. Notare che i test unitari falliti nel bagagliaio possono disturbare il resto della squadra, poiché richiedono a tutti di verificare se la sua ultima modifica non ha causato l'errore.

In un ramo di gestione temporanea o di rilascio, i test non riusciti sono "allarme rosso", a indicare che è stato fatto qualcosa di completamente sbagliato in alcuni changeset, quando è stato unito dal trunk al ramo di rilascio.

Vorrei anche suggerire che anche il rilascio di software con test unitari non riusciti non è male.

Rilasciare software con alcuni bug noti al di sotto di una certa gravità non è necessariamente negativo. Tuttavia, questi problemi noti non dovrebbero causare un test unitario fallito. Altrimenti, dopo ogni test unitario, si dovranno esaminare i 20 test unitari falliti e verificare uno a uno se l'errore è accettabile o meno. Ciò diventa ingombrante, soggetto a errori e elimina gran parte dell'aspetto dell'automazione dei test unitari.

Se disponi davvero di test per bug noti e accettabili, usa la funzione di disabilitazione / ignora del tuo strumento di test unità (quindi non vengono eseguiti di default, ma solo su richiesta). Inoltre, aggiungi un ticket a bassa priorità al tracker dei problemi, in modo che il problema non venga dimenticato.


18
Penso che questa sia la vera risposta. OP menziona "processo di rilascio" e "alcune schermate [che mostrano i risultati dei test]", che suona come un server di build. Il rilascio non è lo stesso dello sviluppo (non sviluppare in produzione!); va bene avere test falliti in sviluppo, sono come TODO; dovrebbero essere tutti verdi (FATTO) quando vengono inviati al server di compilazione.
Warbo,

7
Una risposta molto migliore di quella più votata. Mostra una comprensione di da dove proviene l'operazione senza insegnare loro su una situazione del mondo ideale, riconosce la possibilità di bug noti (per i quali non viene eliminata l'intera tabella di marcia per correggere alcuni rari casi d'angolo) e spiega che i test unitari dovrebbero solo definitivamente essere verde in un ramo / processo di rilascio.
Sebastiaan van den Broek,

5
@SebastiaanvandenBroek: grazie per la tua risposta positiva. Giusto per chiarire questo: i test unitari falliti IMHO dovrebbero essere rari anche nel bagagliaio, poiché ottenere tali fallimenti troppo spesso disturberà l'intero team, non solo quello che ha apportato la modifica che ha causato l'errore.
Doc Brown,

4
Penso che il problema qui sia pensare che tutti i test automatici siano unit test. Molti framework di test includono la possibilità di contrassegnare i test che dovrebbero fallire (spesso chiamati XFAIL). (Questo è diverso da un test che richiede un risultato di errore. I test XFAIL avrebbero idealmente successo, ma non lo fanno.) La suite di test continua comunque a fallire. Il caso d'uso più comune sono le cose che falliscono solo su alcune piattaforme (e sono solo XFAIL su quelle), ma usare la funzione per tracciare qualcosa che richiederà troppo lavoro per essere risolto in questo momento è anche ragionevole. Ma questi tipi di test non sono generalmente test unitari.
Kevin Cathcart,

1
+1, anche se suggerisco una leggera aggiunta (in grassetto) a questa frase: "Questo diventa ingombrante, soggetto a errori, condiziona le persone a ignorare i guasti nella suite di test come rumore e scarta una grande parte dell'aspetto dell'automazione dei test unitari .
mtraceur

228

... tutti i test unitari passano in verde - il che dovrebbe essere buono.

Si è buono. Nessun "dovrebbe essere" al riguardo.

Promuove l'idea che il codice dovrebbe essere perfetto e che non dovrebbero esistere bug - che nel mondo reale è sicuramente impossibile per un programma di qualsiasi dimensione.

No. Dimostra che hai testato il codice e puoi farlo fino a questo punto. È del tutto possibile che i test non coprano tutti i casi. In tal caso, eventuali errori verranno visualizzati nelle segnalazioni di errori e si scriveranno test [non riusciti] per riprodurre i problemi e quindi correggere l'applicazione in modo che i test vengano superati.

È disincentivo pensare a unit test che falliranno.

Test non riusciti o negativi pongono limiti fermi a ciò che la tua candidatura sarà e non accetterà. La maggior parte dei programmi che conosco si opporranno a una "data" del 30 febbraio. Inoltre, gli sviluppatori, i tipi creativi che siamo, non vogliono rompere "i loro bambini". L'attenzione che ne risulta sui casi "felici" porta a fragili applicazioni che si rompono - spesso.

Per confrontare la mentalità dello sviluppatore e del tester:

  • Uno sviluppatore si ferma non appena il codice fa quello che vuole.
  • Un tester si interrompe quando non è più possibile interrompere il codice.

Queste sono prospettive radicalmente diverse e difficili da riconciliare per molti sviluppatori.

O sicuramente escogitare test unitari che sarebbero difficili da risolvere.

Non scrivi test per farti lavorare da solo. Scrivi test per assicurarti che il tuo codice stia facendo quello che dovrebbe fare e, cosa ancora più importante, che continui a fare quello che dovrebbe fare dopo aver modificato la sua implementazione interna.

  • Il debug "dimostra" che il codice fa quello che vuoi oggi .
  • I test "dimostrano" che il codice fa ancora quello che vuoi nel tempo .

Se in qualsiasi momento tutti i test unitari superano, allora non esiste un quadro generale dello stato del software in nessun momento. Non esiste una tabella di marcia / obiettivo.

L'unico test "immagine" fornito è un'istantanea che il codice "funziona" nel momento in cui è stato testato. Come si evolve dopo è una storia diversa.

Rileva i test delle unità di scrittura in anticipo - prima dell'implementazione.

Questo è esattamente quello che dovresti fare. Scrivi un test fallito (perché il metodo che sta testando non è ancora stato implementato) quindi scrivi il codice del metodo per far funzionare il metodo e, quindi, il test passa. Questo è praticamente il punto cruciale dello sviluppo guidato dai test.

Vorrei anche suggerire che anche il rilascio di software con test unitari non riusciti non è male. Almeno allora sai che alcuni aspetti del software hanno dei limiti.

Rilasciare il codice con test non funzionanti significa che parte della sua funzionalità non funziona più come prima. Questo potrebbe essere un atto deliberato perché hai corretto un bug o migliorato una funzione (ma poi avresti dovuto modificare prima il test in modo che fallisse, quindi codificare la correzione / miglioramento, facendo funzionare il test nel processo). Ancora più importante: siamo tutti umani e facciamo errori. Se rompi il codice, dovresti interrompere i test e quei test rotti dovrebbero far suonare le campane di allarme.

Non è vivere in un mondo da sogno?

Se non altro, è vivere nel mondo reale , riconoscendo che gli sviluppatori non sono né onniscienti né infallable, che ci fanno commettere errori e che abbiamo bisogno di una rete di sicurezza per la cattura di noi se e quando ci facciamo rovinare!
Inserisci i test.

E in realtà non scoraggia una vera comprensione del codice?

Forse. Non hai necessariamente bisogno di capire l'implementazione di qualcosa per scrivere test per questo (che fa parte del loro punto). I test definiscono il comportamento e i limiti dell'applicazione e assicurano che rimangano gli stessi a meno che tu non li modifichi deliberatamente.


7
@Tibos: disabilitare un test è come commentare una funzione. Hai il controllo della versione. Usalo
Kevin,

6
@Kevin Non so cosa intendi per 'usalo'. Contrassegno un test come "ignorato" o "in sospeso" o in qualsiasi convenzione venga utilizzata dal mio test runner e commetto quel tag skip per il controllo della versione.
apertura del

4
@dcorking: voglio dire non commentare il codice, eliminarlo. Se successivamente decidi di averne bisogno, ripristinalo dal controllo versione. Effettuare un test disabilitato non è diverso.
Kevin,

4
"È del tutto possibile che i tuoi test non coprano tutti i casi." Andrei così lontano da dire che per ogni pezzo di codice non banale testato, sicuramente non hai coperto tutti i casi.
corsiKa

6
@Tibos I sostenitori del test unitario affermano che il tempo di ciclo dalla scrittura di un test fallito alla scrittura del codice per esso dovrebbe essere ridotto (ad es. 20 minuti. Alcuni sostengono 30 secondi). Se non hai tempo per scrivere immediatamente il codice, è probabilmente troppo complesso. Se non è complesso, eliminare il test in quanto può essere riscritto se la funzione rilasciata viene aggiunta di nuovo. Perché non commentarlo? Non sai che la funzione verrà mai aggiunta di nuovo, quindi il test (o codice) commentato è solo rumore.
CJ Dennis,

32

Perché i test unitari non vengono visti come negativi?

Non lo sono: lo sviluppo guidato dai test si basa sull'idea di fallire i test. Fallimento dei test unitari per guidare lo sviluppo, fallimento dei test di accettazione per guidare una storia ...

Quello che ti manca è il contesto ; dove possono fallire i test unitari?

La solita risposta è che i test unitari possono fallire solo nei sandbox privati.

L'idea di base è questa: in un ambiente in cui i test falliti sono condivisi, ci vuole uno sforzo in più per capire se una modifica al codice di produzione ha introdotto un nuovo errore. La differenza tra zero e non zero è molto più facile da rilevare e gestire rispetto alla differenza tra N e non N.

Inoltre, mantenere pulito il codice condiviso significa che gli sviluppatori possono rimanere in attività. Quando unisco il tuo codice, non ho bisogno di spostare i contesti dal problema che mi viene pagato per risolvere la calibrazione della mia comprensione di quanti test dovrebbero fallire. Se il codice condiviso sta superando tutti i test, tutti gli errori che compaiono quando mi unisco nelle mie modifiche devono far parte dell'interazione tra il mio codice e la linea di base pulita esistente.

Allo stesso modo, durante l'imbarco un nuovo sviluppatore può diventare produttivo più rapidamente, poiché non ha bisogno di perdere tempo a scoprire quali test falliti sono "accettabili".

Per essere più precisi: la disciplina è che devono passare i test eseguiti durante la compilazione.

Come meglio posso dire, non c'è nulla di sbagliato nell'avere test falliti che sono disabilitati .

Ad esempio, in un ambiente di "integrazione continua", condividerai il codice con cadenza elevata. L'integrazione spesso non significa necessariamente che le modifiche debbano essere pronte per il rilascio. Esistono diverse tecniche di distribuzione scure che impediscono il rilascio del traffico in sezioni del codice fino a quando non sono pronte.

Queste stesse tecniche possono essere utilizzate anche per disabilitare i test falliti.

Uno degli esercizi che ho affrontato in un rilascio puntuale riguardava lo sviluppo di un prodotto con molti test falliti. La risposta che ci è venuta è stata semplicemente di passare attraverso la suite, disabilitando i test falliti e documentandoli ciascuno. Ciò ci ha permesso di raggiungere rapidamente un punto in cui tutti i test abilitati stavano superando e il management / donatore obiettivo / proprietario dell'oro poteva vedere tutti gli scambi che avevamo fatto per arrivare a quel punto e prendere decisioni informate sulla pulizia rispetto al nuovo lavoro.

In breve: esistono altre tecniche per tenere traccia del lavoro che non lasciare un mucchio di test falliti nella suite in esecuzione.


Avrei detto "Non c'è ... niente di sbagliato nell'avere test falliti che sono disabilitati ".
CJ Dennis,

Quel cambiamento chiarisce certamente il significato. Grazie.
VoiceOfUnreason,

26

Ci sono molte ottime risposte, ma vorrei aggiungere un'altra prospettiva che credo non sia ancora ben coperta: qual è esattamente il punto di fare i test.

I test unitari non sono lì per verificare che il codice sia privo di bug.

Penso che questo sia il malinteso principale. Se questo fosse il loro ruolo, ti aspetteresti davvero di avere test falliti dappertutto. Ma invece,

I test unitari controllano che il tuo codice faccia quello che pensi che faccia.

In casi estremi può includere la verifica che i bug noti non vengano corretti . Il punto è avere il controllo sulla base di codice ed evitare modifiche accidentali. Quando apporti una modifica, va bene e in realtà si prevede che supererà alcuni test: stai cambiando il comportamento del codice. Il test appena interrotto è ora una buona traccia di ciò che hai cambiato. Verifica che tutte le rotture siano conformi a ciò che desideri dal tuo cambiamento. In tal caso, basta aggiornare i test e continuare. Altrimenti, il tuo nuovo codice è sicuramente difettoso, torna indietro e correggilo prima di inviarlo!

Ora, tutto quanto sopra funziona solo se tutti i test sono verdi, dando risultati fortemente positivi: questo è esattamente come funziona il codice. I test rossi non hanno quella proprietà. "Questo è ciò che questo codice non fa" è raramente un'informazione utile.

I test di accettazione possono essere ciò che stai cercando.

Esistono test di accettazione. È possibile scrivere una serie di test che devono essere soddisfatti per chiamare il prossimo traguardo. Questi sono ok per essere rossi, perché è quello per cui sono stati progettati. Ma sono molto diversi dai test unitari e non possono né dovrebbero sostituirli.


2
Una volta ho dovuto sostituire una biblioteca con un'altra. I test unitari mi hanno aiutato a garantire che tutti i casi corner fossero ancora trattati in modo identico dal nuovo codice.
Thorbjørn Ravn Andersen,

24

Lo considero l'equivalente del software della sindrome della finestra rotta .

I test di lavoro mi dicono che il codice è di una data qualità e che i proprietari del codice se ne preoccupano.

Per quanto riguarda quando dovresti preoccuparti della qualità, questo dipende piuttosto dal ramo / repository del codice sorgente su cui stai lavorando. Il codice di sviluppo potrebbe anche avere dei test rotti che indicano lavori in corso (si spera!).

I test interrotti su un ramo / repository per un sistema live dovrebbero immediatamente far suonare le campane di allarme. Se i test non funzionanti possono continuare a fallire o se sono contrassegnati permanentemente come "ignora", aspettati che il loro numero aumenti nel tempo. Se questi non vengono riesaminati regolarmente, sarà stato impostato il precedente che è OK lasciare test non funzionanti.

I test non funzionanti sono visti in modo così peggiorativo in molti negozi da avere una limitazione sulla possibilità di commettere codici non funzionanti .


9
Se i test documentano il modo in cui un sistema è, dovrebbero certamente passare sempre - se non lo sono, significa che gli invarianti sono rotti. Ma se documentano il modo in cui dovrebbe essere un sistema , anche i test falliti possono essere utili - purché il framework di test delle unità supporti un buon modo per contrassegnarli come "problemi noti" e se li colleghi a un elemento nel tracker dei problemi. Penso che entrambi gli approcci abbiano il loro merito.
Luaan,

1
@Luaan Sì, questo presuppone piuttosto che tutti i test unitari siano creati allo stesso modo. Certamente non è insolito che i responsabili della costruzione suddividano e tagliano i test tramite alcuni attributi a seconda della loro durata, della loro fragilità e di vari altri criteri.
Robbie Dee,

Questa risposta è ottima per la mia esperienza personale. Una volta che alcune persone si abituano a ignorare una serie di test falliti o a infrangere le migliori pratiche in alcuni punti, lascia passare un paio di mesi e vedrai una percentuale di test ignorati che aumenta drasticamente, la qualità del codice scende al livello di "hack-script" . E sarà molto difficile ricordare a tutti il ​​processo.
usr-local-ΕΨΗΕΛΩΝ

11

Ecco la fallacia logica sottostante:

Se è buono quando tutti i test passano, allora deve essere cattivo se tutti i test falliscono.

Con test di unità, esso È bene quando tutti i test passano. È ANCHE BUONO quando un test fallisce. I due non devono essere in opposizione.

Un test fallito è un problema rilevato dalla tua attrezzatura prima che raggiungesse un utente. È un'opportunità per correggere un errore prima che venga pubblicato. E questa è una buona cosa.


Linea di pensiero interessante. Vedo l'errore della domanda più simile a questo: "poiché è buono quando un test unitario fallisce, è male quando tutti i test passano".
Doc Brown,

Mentre il tuo ultimo paragrafo è un buon punto, sembra che il problema sia più un fraintendimento di "in qualsiasi momento nel tempo tutti i test unitari devono passare" (come indica la risposta accettata) e il punto dei test unitari.
Dukeling,

9

La risposta di Phill W è ottima. Non posso sostituirlo.

Tuttavia, voglio concentrarmi su un'altra parte che potrebbe essere stata parte della confusione.

In alcune organizzazioni, a quanto pare, parte del processo di rilascio del software consiste nell'utilizzare i test unitari, ma in qualsiasi momento tutti i test unitari devono superare

"in qualsiasi momento" sta esagerando nel tuo caso. Quello che è importante è che i test di unità passano dopo un certo cambiamento è stato attuato, prima di iniziare a implementare un altro cambiamento.
Ecco come tenere traccia di quale modifica ha causato l'insorgere di un bug. Se i test unitari hanno iniziato a fallire dopo aver implementato la modifica 25 ma prima di implementare la modifica 26, allora sai che la modifica 25 ha causato l'errore.

Durante l'implementazione di una modifica, ovviamente i test unitari potrebbero fallire; tat dipende molto da quanto è grande il cambiamento. Se sto riqualificando una funzionalità di base, che è più di una semplice modifica, probabilmente interromperò i test per un po 'fino a quando non avrò finito di implementare la mia nuova versione della logica.


Questo può creare conflitti per quanto riguarda le regole del team. In realtà l'ho incontrato poche settimane fa:

  • Ogni commit / push provoca una build. La compilazione non deve mai fallire (se fallisce o fallisce qualsiasi test, lo sviluppatore committente è incolpato).
  • Ci si aspetta che ogni sviluppatore invii le proprie modifiche (anche se incomplete) alla fine della giornata, in modo che i team leader possano rivedere il codice al mattino.

Entrambe le regole andrebbero bene. Ma entrambe le regole non possono funzionare insieme. Se mi viene assegnato un cambiamento importante che richiede diversi giorni per completare, non sarei in grado di aderire ad entrambe le regole contemporaneamente. A meno che non commentassi i miei cambiamenti ogni giorno e li commettessi senza commenti dopo aver fatto tutto; che è solo un lavoro senza senso.

In questo scenario, il problema qui non è che i test unitari non hanno scopo; è che la società ha aspettative non realistiche . Il loro insieme di regole arbitrarie non copre tutti i casi e il mancato rispetto delle regole è considerato ciecamente come un fallimento dello sviluppatore piuttosto che un fallimento della regola (che è, nel mio caso).


3
L'unico modo in cui questo può funzionare è utilizzare la ramificazione, in modo tale che gli sviluppatori si impegnino e spingano per disporre di rami che non hanno bisogno di costruire in modo pulito mentre sono incompleti, ma che si impegnano nel ramo principale innescano una build, che dovrebbe costruire in modo pulito.
Gwyn Evans,

1
Applicare la spinta a cambiamenti incompleti è assurdo, non vedo alcuna giustificazione per farlo. Perché non riesaminare il codice una volta completata la modifica?
Callum Bradbury,

Bene, per esempio, è un modo rapido per garantire che il codice non sia solo sul laptop / workstation dello sviluppatore se il loro disco rigido dovesse smettere di funzionare o andrebbe altrimenti perso - se c'è una politica di impegno anche se nel mezzo del lavoro, allora c'è una quantità limitata di lavoro a rischio.
Gwyn Evans,

1
I flag delle caratteristiche risolvono il paradosso apparente.
RubberDuck,

1
@Flater sì, anche per rielaborare la logica esistente.
RubberDuck,

6

Se non si risolvono tutti i test unitari, è possibile entrare rapidamente nello stato in cui nessuno corregge test non funzionanti.

  1. Non è corretto poiché il superamento dei test unitari non mostra che il codice sia perfetto

  2. È disincentivo trovare un codice che sarebbe difficile da testare, il che è positivo dal punto di vista del design

  3. La copertura del codice può essere d'aiuto (anche se non è una panacea). Anche i test unitari sono solo un aspetto del test: anche tu vuoi test di integrazione / accettazione.


6

Per aggiungere alcuni punti alle risposte già buone ...

ma in qualsiasi momento devono passare tutti i test unitari

Ciò dimostra una mancanza di comprensione di un processo di rilascio. Un fallimento del test può indicare una funzionalità pianificata sotto TDD che non è ancora stata implementata; oppure può indicare un problema noto che prevede una correzione per una versione futura; o potrebbe essere semplicemente qualcosa in cui il management ha deciso che questo non è abbastanza importante da risolvere perché è improbabile che i clienti se ne accorgano. La cosa fondamentale che tutti condividono è che il management ha emesso un giudizio sul fallimento.

Promuove l'idea che il codice dovrebbe essere perfetto e che non dovrebbero esistere bug - che nel mondo reale è sicuramente impossibile per un programma di qualsiasi dimensione.

Altre risposte hanno coperto i limiti dei test.

Non capisco perché pensi che eliminare i bug sia un aspetto negativo però. Se non vuoi fornire il codice che hai controllato (al meglio delle tue capacità) fa quello che dovrebbe, perché lavori anche nel software?

Se in qualsiasi momento tutti i test unitari superano, allora non esiste un quadro generale dello stato del software in nessun momento. Non esiste una tabella di marcia / obiettivo.

Perché deve esserci una tabella di marcia?

I test unitari inizialmente controllano che la funzionalità funzioni, ma poi (come test di regressione) verificano che non abbia inavvertitamente rotto nulla. Per tutte le funzionalità con test unitari esistenti, non esiste una tabella di marcia . È noto che ogni funzione funziona (entro i limiti dei test). Se quel codice è finito, non ha una tabella di marcia perché non è necessario altro lavoro su di esso.

Come ingegneri professionisti, dobbiamo evitare la trappola della doratura. Gli hobbisti possono permettersi di perdere tempo a armeggiare sui bordi con qualcosa che funzioni. Come professionisti, dobbiamo consegnare un prodotto. Ciò significa che facciamo funzionare qualcosa, verificiamo che funzioni e passiamo al lavoro successivo.


6

Promuove l'idea che il codice dovrebbe essere perfetto e che non dovrebbero esistere bug - che nel mondo reale è sicuramente impossibile per un programma di qualsiasi dimensione.

Non vero. perché pensi che sia impossibile? qui un esempio per il programma che funziona:

public class MyProgram {
  public boolean alwaysTrue() {
    return true;
  }

  @Test
  public void testAlwaysTrue() {
    assert(alwaysTrue() == true);
  }
}

È disincentivo pensare a unit test che falliranno. O sicuramente escogitare test unitari che sarebbero difficili da risolvere.

In tal caso potrebbe non essere un test unitario, ma un test di integrazione se è complicato

Se in qualsiasi momento tutti i test unitari superano, allora non esiste un quadro generale dello stato del software in nessun momento. Non esiste una tabella di marcia / obiettivo.

vero, si chiama unit test per un motivo, controlla una piccola unità di codice.

Rileva i test delle unità di scrittura in anticipo - prima dell'implementazione.

Sviluppatori volontàscoraggiare la scrittura di eventuali test se non ne comprendono i beneficiper loro natura (a meno che non provengano dal QA)


"Gli sviluppatori dissuaderanno [sic] dalla scrittura di qualsiasi test per loro natura" - questa è una totale assurdità. Lavoro in un'intera compagnia di sviluppatori che praticano TDD e BDD.
RubberDuck,

@RubberDuck Ho provato a rispondere a un "fatto" in questione e stavo esagerando. Aggiornerò
user7294900

"X sarà dissuaso dal fare Y se non capiscono i benefici di Y" si applica praticamente per qualsiasi X e Y, quindi questa affermazione probabilmente non è particolarmente utile. Probabilmente avrebbe più senso spiegare i vantaggi della stesura dei test e in particolare di farlo in anticipo.
Dukeling,

2
"impossibile per un programma di qualsiasi dimensione" non significa "tutti i programmi, indipendentemente dalle dimensioni", significa "qualsiasi programma significativo (con una lunghezza non banale)" Il tuo tentato contro-esempio è inapplicabile, perché non è ' t un programma significativo e utile.
Ben Voigt,

@BenVoigt Non credo di dover dare un "programma significativo" come risposta.
user7294900,

4

Promuove l'idea che il codice dovrebbe essere perfetto e che non dovrebbero esistere bug

Assolutamente no. Promuove l'idea che i test non dovrebbero fallire, niente di più e niente di meno. Supponendo che avere dei test (anche molti di essi) dica qualcosa su "perfetto" o "nessun bug" è un errore. Decidere quanto bassi o profondi dovrebbero essere i tuoi test è una parte significativa della stesura di buoni test e il motivo per cui abbiamo categorie di test distintamente separate (test "unit", test di integrazione, "scenari" nel senso del cetriolo ecc.).

È disincentivo pensare a unit test che falliranno. O sicuramente escogitare test unitari che sarebbero difficili da risolvere.

Nello sviluppo guidato dai test, è obbligatorio che ogni test unitario fallisca prima di iniziare a programmare. Si chiama "ciclo rosso-verde" (o "ciclo rosso-verde-refattore") proprio per questo motivo.

  • Senza il fallimento del test, non si sa se il codice è effettivamente testato dal test. I due potrebbero non essere affatto collegati.
  • Cambiando il codice per esattamente fare il giro di prova dal rosso al verde, niente di più e niente di meno, si può essere abbastanza sicuri che il codice fa quello che deve fare, e non molto di più (che si potrebbe mai bisogno).

Se in qualsiasi momento tutti i test unitari superano, allora non esiste un quadro generale dello stato del software in nessun momento. Non esiste una tabella di marcia / obiettivo.

I test sono più una specie di micro-obiettivo. Nello sviluppo test-driven, il programmatore scriverà prima un test (singolare), quindi avrà un chiaro obiettivo di implementare del codice; quindi il prossimo test e così via.

La funzione dei test non è quella di essere lì per completezza prima che il codice venga scritto.

Se eseguito correttamente, in una lingua e con una libreria di test che ben si adatta a questo approccio, ciò può effettivamente accelerare enormemente lo sviluppo, poiché i messaggi di errore (eccezioni / stack stack) possono indirizzare direttamente lo sviluppatore verso dove deve eseguire il lavoro Il prossimo.

Rileva i test delle unità di scrittura in anticipo - prima dell'implementazione.

Non vedo come questa affermazione sarebbe vera. La scrittura di test dovrebbe idealmente far parte dell'implementazione.

Mi sto perdendo qualcosa qui? Perché le organizzazioni prevedono che tutti i test unitari vengano superati?

Perché le organizzazioni si aspettano che i test abbiano rilevanza per il codice. Scrivere test riusciti significa che hai documentato una parte della tua applicazione e hai dimostrato che l'applicazione fa quello che dice (il test). Niente di più e niente di meno.

Inoltre, una parte molto importante dell'avere i test è la "regressione". Volete essere in grado di sviluppare o riformattare il nuovo codice con fiducia. Avere una grande quantità di test ecologici ti consente di farlo.

Questo va dall'organizzazione al livello psicologico. Uno sviluppatore che sa che i suoi errori saranno probabilmente colti dai test sarà molto più libero di trovare soluzioni intelligenti e audaci per i problemi che deve risolvere. D'altra parte, uno sviluppatore che non ha test sarà, dopo un po 'di tempo, a un punto morto (a causa della paura) perché non sa mai se un cambiamento che interrompe il resto dell'applicazione.

Non è vivere in un mondo da sogno?

No. Lavorare con un'applicazione guidata dai test è pura gioia, a meno che non ti piaccia il concetto per qualsiasi motivo ("maggiore sforzo" ecc.) Di cui possiamo discutere in un'altra domanda.

E in realtà non scoraggia una vera comprensione del codice?

Assolutamente no, perché dovrebbe?

Trovi molti grandi progetti open source (per i quali la gestione della "comprensione" e del know-how sul codice è un argomento molto pressante) che effettivamente utilizzano i test come documentazione principale del software, oltre a essere test, fornire anche esempi reali, funzionanti e sintatticamente corretti per utenti o sviluppatori dell'applicazione / libreria. Questo spesso funziona magnificamente.

Ovviamente, scrivere test negativi è negativo. Ma ciò non ha nulla a che fare con la funzione dei test in sé.


3

(Dai miei commenti originali)

C'è una differenza tra funzionalità richiesta e obiettivi futuri. I test sono per la funzionalità richiesta: sono precisi, formali, eseguibili e se falliscono il software non funziona. Gli obiettivi futuri potrebbero non essere precisi o formali, per non parlare dell'eseguibile, quindi è meglio lasciarli in un linguaggio naturale come in tracker di problemi / bug, documentazione, commenti, ecc.

Come esercizio, prova a sostituire la frase "unit test" nella tua domanda con "errore del compilatore" (o "errore di sintassi", se non è presente alcun compilatore). È ovvio che una versione non dovrebbe contenere errori del compilatore, poiché sarebbe inutilizzabile; tuttavia gli errori del compilatore e gli errori di sintassi sono la normale situazione della macchina di uno sviluppatore quando scrivono codice. Gli errori scompaiono solo quando hanno finito; ed è esattamente quando il codice dovrebbe essere spinto. Ora sostituisci "errore del compilatore" in questo paragrafo con "unit test" :)


2

Lo scopo dei test automatici è di dirti quando hai rotto qualcosa il prima possibile . Il flusso di lavoro è un po 'così:

  1. Fare un cambiamento
  2. Crea e testa le tue modifiche (idealmente automaticamente)
  3. Se i test falliscono, significa che hai rotto qualcosa che prima funzionava
  4. se i test superano, dovresti essere sicuro che la modifica non ha introdotto nuove regressioni (a seconda della copertura del test)

Se i tuoi test fallivano già, il passaggio 3 non funziona nel modo più efficace: i test falliranno, ma non sai se ciò significa che hai rotto qualcosa o no senza indagare. Forse potresti contare il numero di test falliti, ma una modifica potrebbe correggere un bug e romperne un altro, oppure un test potrebbe iniziare a fallire per un motivo diverso. Ciò significa che è necessario attendere un po 'di tempo prima di sapere se qualcosa è stato risolto, o fino a quando tutti i problemi sono stati risolti o fino a quando ogni test non è stato esaminato.

La capacità dei test unitari di individuare i bug appena introdotti il ​​più presto possibile è la cosa più preziosa dei test automatici: più a lungo un difetto viene scoperto e più è costoso da correggere.

Promuove l'idea che il codice dovrebbe essere perfetto e che non dovrebbero esistere bug
È disincentivo pensare a test unitari che falliranno

I test per le cose che non funzionano non vi dico nulla - scrivere unit test per le cose che fanno il lavoro, o che sono in procinto di risolvere. Ciò non significa che il tuo software sia privo di difetti, significa che nessuno dei difetti per cui hai precedentemente scritto unit test è tornato.

Rileva i test delle unità di scrittura in anticipo

Se funziona per te, scrivi i test in anticipo, non controllarli nel tuo master / trunk fino a quando non passano.

Se in qualsiasi momento tutti i test unitari superano, allora non esiste un quadro generale dello stato del software in nessun momento. Non esiste una tabella di marcia / obiettivo.

I test unitari non servono per impostare una roadmap / obiettivo, forse usare un backlog per quello? Se tutti i test vengono superati, il "quadro generale" è che il software non è danneggiato (se la copertura dei test è buona). Molto bene!


2

Le risposte esistenti sono certamente buone, ma non ho visto nessuno affrontare questo equivoco fondamentale nella domanda:

in qualsiasi momento devono passare tutti i test unitari

No. Sicuramente, questo non sarà vero. Durante lo sviluppo di software, NCrunch è spesso marrone (errore di compilazione) o rosso (test non riuscito).

Dove NCrunch deve essere verde (tutti i test che superano) è quando sono pronto a inviare un commit al server di controllo del codice sorgente, perché a quel punto altri potrebbero dipendere dal mio codice.

Ciò alimenta anche l'argomento della creazione di nuovi test: i test dovrebbero far valere la logica e il comportamento del codice. Condizioni al contorno, condizioni di errore, ecc. Quando scrivo nuovi test, provo a identificare questi "punti critici" nel codice.

I test unitari documentano come mi aspetto che venga chiamato il mio codice: presupposti, risultati previsti, ecc.

Se un test si interrompe a seguito di una modifica, devo decidere se il codice o il test sono in errore.


Come nota a margine, i test unitari a volte vanno di pari passo con Test Driven Development. Uno dei principi di TDD è che i test rotti sono i tuoi punti di riferimento. Quando un test ha esito negativo, è necessario correggere il codice in modo che il test abbia esito positivo. Ecco un esempio concreto dall'inizio di questa settimana:

Background : ho scritto e ora supporto una libreria utilizzata dai nostri sviluppatori che viene utilizzata per convalidare le query Oracle. Avevamo test che affermavano che la query corrispondeva a un valore atteso, il che rendeva il caso importante (non è in Oracle) e approvava allegramente le query non valide purché corrispondessero completamente al valore previsto.

Invece, la mia libreria analizza la query utilizzando Antlr e una sintassi Oracle 12c, quindi avvolge varie asserzioni sull'albero della sintassi stesso. Cose come, è valido (non sono stati generati errori di analisi), tutti i suoi parametri sono soddisfatti dalla raccolta dei parametri, tutte le colonne previste lette dal lettore di dati sono presenti nella query, ecc. Tutti questi sono elementi che sono passati a produzione in vari momenti.

Uno dei miei colleghi ingegneri mi ha inviato una query lunedì che era fallita (o meglio, era riuscita quando avrebbe dovuto fallire) durante il fine settimana. La mia libreria ha detto che la sintassi andava bene, ma è esplosa quando il server ha tentato di eseguirla. E quando ha guardato la domanda, era ovvio il perché:

UPDATE my_table(
SET column_1 = 'MyValue'
WHERE id_column = 123;

Ho caricato il progetto e aggiunto un test unit che ha affermato che questa query non dovrebbe essere valida. Ovviamente, il test ha fallito.

Successivamente, ho eseguito il debug del test non riuscito, ho esaminato il codice in cui mi aspettavo che generasse l'eccezione e ho capito che Antlr stava generando un errore sulla finestra aperta, ma non nel modo previsto dal codice precedente. Ho modificato il codice, verificato che il test ora era verde (superando) e che nessun altro aveva interrotto il processo, impegnato e spinto.

Ciò ha richiesto forse 20 minuti e nel frattempo ho effettivamente migliorato notevolmente la libreria perché ora supportava un'intera gamma di errori che in precedenza aveva ignorato. Se non avessi test unitari per la biblioteca, la ricerca e la risoluzione del problema avrebbero potuto richiedere ore.


0

Un punto che non credo esca dalle risposte precedenti è che c'è una differenza tra test interni e test esterni (e penso che molti progetti non siano abbastanza accurati per distinguerli). Un test interno verifica che alcuni componenti interni funzionino come dovrebbero; un test esterno mostra che il sistema nel suo complesso funziona come dovrebbe. È del tutto possibile, ovviamente, avere guasti nei componenti che non comportano un guasto del sistema (forse c'è una caratteristica del componente che il sistema non usa, o forse il sistema recupera da un guasto del componente). Un errore del componente che non provoca un errore del sistema non dovrebbe impedirti di rilasciarlo.

Ho visto progetti paralizzati dal fatto di avere troppi test sui componenti interni. Ogni volta che si tenta di implementare un miglioramento delle prestazioni, si interrompono dozzine di test, poiché si sta modificando il comportamento dei componenti senza effettivamente modificare il comportamento visibile esternamente del sistema. Ciò porta a una mancanza di agilità nel progetto nel suo insieme. Credo che gli investimenti nei test di sistemi esterni abbiano generalmente un risultato molto migliore rispetto agli investimenti nei test di componenti interni, specialmente quando si parla di componenti di livello molto basso.

Quando suggerisci che i test unitari falliti non contano davvero, mi chiedo se questo è ciò che hai in mente? Forse dovresti valutare il valore dei test unitari e abbandonare quelli che causano più problemi di quanti ne valgano la pena, concentrandoti maggiormente sui test che verificano il comportamento visibile dall'esterno dell'applicazione.


Penso che ciò che stai descrivendo come "test esterni" sia spesso descritto altrove come test di "integrazione".
GalacticCowboy,

Sì, ma ho riscontrato differenze nella terminologia. Per alcune persone, i test di integrazione riguardano più la configurazione software / hardware / rete distribuita, mentre sto parlando del comportamento esterno di un software che stai sviluppando.
Michael Kay,

0

"ma in qualsiasi momento tutti i test unitari devono superare"

Se questo è l'atteggiamento nella tua azienda, è un problema. In un determinato momento, vale a dire quando dichiariamo che il codice è pronto per passare all'ambiente successivo, tutti i test unitari dovrebbero passare. Ma durante lo sviluppo, dovremmo aspettarci di routine che molti test unitari falliscano.

Nessuna persona ragionevole si aspetta che un programmatore ottenga il suo lavoro perfetto al primo tentativo. Quello che ci aspettiamo ragionevolmente è che continuerà a lavorarci fino a quando non ci saranno problemi noti.

"È un disincentivo pensare a test unitari che falliranno. O certamente escogitare test unitari che sarebbero difficili da risolvere." Se qualcuno nella tua organizzazione pensa che non dovrebbe menzionare un possibile test perché potrebbe non riuscire e causare più lavoro per risolverlo, quella persona è totalmente non qualificata per il suo lavoro. Questo è un atteggiamento disastroso. Vorresti un dottore che dicesse: "Quando faccio un intervento chirurgico, non controllo deliberatamente se i punti sono corretti, perché se vedo che non lo sono dovrò tornare indietro e rifarli rallenterà terminando l'operazione "?

Se il team è ostile ai programmatori che identificano gli errori prima che il codice vada in produzione, hai un vero problema con l'atteggiamento di quel team. Se la direzione punisce i programmatori che identificano errori che rallentano la consegna, le probabilità sono che la tua azienda sia diretta verso il fallimento.

Sì, è certamente vero che a volte le persone razionali dicono: "Ci stiamo avvicinando alla scadenza, questo è un problema banale e non vale la pena dedicare le risorse proprio ora che ci vorrebbe per risolverlo". Ma non puoi prendere questa decisione razionalmente se non lo sai. Esaminare freddamente un elenco di errori e assegnare priorità e programmi per risolverli è razionale. Rendersi deliberatamente ignoranti dei problemi, quindi non è necessario prendere questa decisione è sciocco. Pensi che il cliente non lo scoprirà solo perché non volevi saperlo?


-7

Questo è un esempio specifico di errore di conferma , in cui le persone tendono a cercare informazioni che confermano le loro convinzioni esistenti.

Un esempio famoso di ciò che accade è nel gioco 2,4,6.

  • Ho una regola nella mia testa che qualsiasi serie di tre numeri passerà o fallirà,
  • 2,4,6 è un passaggio
  • puoi elencare gruppi di tre numeri e ti dirò se passano o falliscono.

Molte persone scelgono una regola, affermano che "il divario tra il 1o e il 2o numero è uguale al divario tra il 2o e il 3o".

Proveranno alcuni numeri:

  • 4, 8, 12? Passaggio
  • 20, 40, 60? Passaggio
  • 2, 1004, 2006? Passaggio

Dicono "Sì, ogni osservazione conferma la mia ipotesi, deve essere vero." E annuncia la loro regola alla persona che dà l'enigma.

Ma non hanno mai ricevuto un singolo "fallimento" per nessun gruppo di tre numeri. La regola avrebbe potuto essere semplicemente "i tre numeri devono essere numeri" per tutte le informazioni che effettivamente hanno.

La regola è in realtà solo che i numeri sono in ordine crescente. Le persone in genere ottengono questo indovinello corretto solo se verificano guasti. La maggior parte delle persone sbaglia, scegliendo una regola più specifica e testando solo i numeri che soddisfano questa regola specifica.

Per quanto riguarda il motivo per cui le persone si innamorano del pregiudizio di conferma e possono vedere i test unitari fallire come prova di un problema, ci sono molti psicologi che possono spiegare il pregiudizio di conferma meglio di me, in pratica si tratta di persone che non amano sbagliare e che lottano per tentare sinceramente per mettersi alla prova.


2
In che modo è pertinente alla domanda? I test unitari non riusciti sono la prova di un problema, per definizione.
Frax,

1
Puoi assolutamente avere unit test che richiedono che il sistema in prova entri in modalità di errore. Non è lo stesso di non vedere mai un test fallito. È anche il motivo per cui TDD è specificato come ciclo "Rosso-> Verde->
Rifattore
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.