È sufficiente utilizzare i test di accettazione e integrazione anziché i test unitari?


62

Breve introduzione a questa domanda. Ho usato TDD e ultimamente BDD per oltre un anno. Uso tecniche come il deridere per rendere più efficiente la scrittura dei miei test. Ultimamente ho avviato un progetto personale per scrivere un piccolo programma di gestione del denaro per me stesso. Dato che non avevo un codice legacy, era il progetto perfetto per iniziare con TDD. Purtroppo non ho provato così tanto la gioia di TDD. Mi ha persino rovinato così tanto il divertimento che ho rinunciato al progetto.

Qual'era il problema? Bene, ho usato l'approccio simile a TDD per consentire ai test / requisiti di evolvere la progettazione del programma. Il problema era che oltre la metà del tempo di sviluppo per quanto riguarda i test di scrittura / refactoring. Quindi alla fine non volevo implementare altre funzionalità perché avrei dovuto refactoring e scrivere a molti test.

Al lavoro ho un sacco di codice legacy. Qui scrivo sempre più test di integrazione e accettazione e meno test unitari. Questo non sembra essere un cattivo approccio poiché i bug sono per lo più rilevati dai test di accettazione e integrazione.

La mia idea era che alla fine avrei potuto scrivere più test di integrazione e accettazione rispetto ai test unitari. Come ho detto per il rilevamento di bug, i test unitari non sono migliori dei test di integrazione / accettazione. Anche i test unitari sono validi per il design. Da quando ne scrivevo molti, le mie lezioni sono sempre progettate per essere testabili. Inoltre, l'approccio che consente ai test / requisiti di guidare la progettazione conduce, nella maggior parte dei casi, a una progettazione migliore. L'ultimo vantaggio dei test unitari è che sono più veloci. Ho scritto abbastanza test di integrazione per sapere che possono essere quasi veloci quanto i test unitari.

Dopo aver guardato attraverso il web ho scoperto che ci sono idee molto simili alle mie menzionate qua e . Cosa ne pensi di quest idea?

modificare

Rispondendo alle domande un esempio in cui il design era buono, ma avevo bisogno di un enorme refactoring per il prossimo requisito:

Inizialmente c'erano alcuni requisiti per eseguire determinati comandi. Ho scritto un parser di comandi estensibile - che analizzava i comandi da una sorta di prompt dei comandi e chiamava quello corretto sul modello. Il risultato è stato rappresentato in una classe del modello di vista: Primo disegno

Non c'era niente di sbagliato qui. Tutte le classi erano indipendenti l'una dall'altra e potevo facilmente aggiungere nuovi comandi, mostrare nuovi dati.

Il requisito successivo era che ogni comando dovesse avere una propria rappresentazione della vista - una sorta di anteprima del risultato del comando. Ho riprogettato il programma per ottenere un design migliore per il nuovo requisito: Secondo disegno

Anche questo era buono perché ora ogni comando ha il suo modello di vista e quindi la sua anteprima.

Il fatto è che il parser dei comandi è stato modificato per utilizzare un'analisi dei comandi basata su token ed è stato rimosso dalla sua capacità di eseguire i comandi. Ogni comando ha il proprio modello di visualizzazione e il modello di visualizzazione dati conosce solo il modello di visualizzazione comandi corrente che non conosce i dati che devono essere mostrati.

Tutto quello che volevo sapere a questo punto è, se il nuovo design non ha violato alcun requisito esistente. Non ho dovuto cambiare QUALUNQUE del mio test di accettazione. Ho dovuto refactoring o eliminare quasi OGNI test unitario, che era un enorme mucchio di lavoro.

Quello che volevo mostrare qui è una situazione comune che si è verificata spesso durante lo sviluppo. Non ci sono stati problemi con il vecchio o il nuovo design, sono semplicemente cambiati naturalmente con i requisiti: come l'ho capito, questo è uno dei vantaggi di TDD, che il design si evolve.

Conclusione

Grazie per tutte le risposte e discussioni. In sintesi di questa discussione ho pensato a un approccio che testerò con il mio prossimo progetto.

  • Prima di tutto scrivo tutti i test prima di implementare qualcosa come ho sempre fatto.
  • Per esigenze scrivo inizialmente alcuni test di collaudo che testano l'intero programma. Quindi scrivo alcuni test di integrazione per i componenti in cui devo implementare il requisito. Se esiste un componente che lavora a stretto contatto con un altro componente per implementare questo requisito, scriverei anche alcuni test di integrazione in cui entrambi i componenti vengono testati insieme. Ultimo ma non meno importante se devo scrivere un algoritmo o qualsiasi altra classe con un'alta permutazione - ad esempio un serializzatore - scriverei test unitari per queste classi particolari. Tutte le altre classi non sono testate ma eventuali test unitari.
  • Per i bug il processo può essere semplificato. Normalmente un bug è causato da uno o due componenti. In questo caso, scriverei un test di integrazione per i componenti che verifica il bug. Se fosse correlato a un algoritmo, scriverei solo un test unitario. Se non è facile rilevare il componente in cui si verifica il bug, scriverei un test di accettazione per individuare il bug - questa dovrebbe essere un'eccezione.


Queste domande sembrano affrontare maggiormente il problema del perché scrivere dei test. Voglio discutere se scrivere test funzionali invece di unit test potrebbe essere un approccio migliore.
Yggdrasil,

secondo la mia lettura, le risposte in una domanda duplicata riguardano principalmente quando i test non unitari hanno più senso
moscerino

Quel primo collegamento è esso stesso un duplicato. Pensi di voler dire: programmers.stackexchange.com/questions/66480/…
Robbie Dee

Le risposte dal link di Robbie Dee riguardano ancora di più il motivo per cui testare.
Yggdrasil,

Risposte:


37

Paragona arance e mele.

Test di integrazione, test di accettazione, test unitari, test comportamentali: sono tutti test e ti aiuteranno a migliorare il tuo codice ma sono anche abbastanza diversi.

A mio avviso, supererò ciascuno dei diversi test e, spero, spiegherò perché è necessario un mix di tutti:

Test di integrazione:

Semplicemente, verifica che le diverse parti del sistema si integrino correttamente, ad esempio, forse simuli una richiesta di servizio Web e verifichi che il risultato ritorni. Generalmente userei dati statici (ish) reali e dipendenze derise per assicurarmi che possano essere verificati in modo coerente.

Test di accettazione:

Un test di accettazione dovrebbe essere direttamente correlato a un caso d'uso aziendale. Può essere enorme ("gli scambi sono inviati correttamente") o minuscolo ("il filtro filtra correttamente un elenco") - non importa; ciò che conta è che dovrebbe essere esplicitamente legato a un requisito specifico dell'utente. Mi piace concentrarmi su questi aspetti per lo sviluppo guidato dai test perché significa che abbiamo un buon manuale di riferimento dei test per le storie degli utenti che dev e qa devono verificare.

Test unitari:

Per piccole unità discrete di funzionalità che possono o non possono costituire da sole una singola storia utente - ad esempio, una storia utente che dice che recuperiamo tutti i clienti quando accediamo a una pagina Web specifica può essere un test di accettazione (simulare di colpire il Web pagina e verifica della risposta) ma può contenere anche diversi test unitari (verifica che le autorizzazioni di sicurezza siano verificate, verifica che la connessione al database richieda correttamente, verifica che qualsiasi codice che limiti il ​​numero di risultati sia eseguito correttamente) - questi sono tutti "test unitari" che non sono un test di accettazione completo.

Test comportamentali:

Definire quale dovrebbe essere il flusso di un'applicazione nel caso di un input specifico. Ad esempio, "quando non è possibile stabilire una connessione, verificare che il sistema ritenti la connessione". Ancora una volta, è improbabile che questo sia un test di accettazione completo, ma ti consente comunque di verificare qualcosa di utile.

Questi sono tutti secondo me attraverso una grande esperienza nella scrittura di test; Non mi piace concentrarmi sugli approcci dei libri di testo, ma piuttosto su ciò che dà valore ai tuoi test.


Nella tua definizione presumo che intendo scrivere più test comportamentali (?) Che test unitari. Unit test per me è un test che verifica una singola classe con tutte le dipendenze derise. Ci sono casi in cui i test unitari sono più utili. Cioè quando scrivo un algoritmo complesso. Quindi ho molti esempi con l'output atteso dell'algoritmo. Voglio testarlo a livello di unità perché in realtà è più veloce del test comportamentale. Non vedo il valore di testare una classe a livello di unità che ha solo una mano piena di percorsi attraverso la classe che può essere facilmente testata da un test comportamentale.
Yggdrasil,

14
Personalmente ritengo che i test di accettazione siano i più importanti, i test di comportamento sono importanti quando si testano cose come comunicazione, affidabilità ed errori e i test unitari sono importanti quando si testano piccole funzionalità complesse (gli algoritmi ne sarebbero un buon esempio)
Michael

Non sono così fermo con la tua terminologia. Programmiamo una suite di programmazione. Lì sono responsabile dell'editor grafico. I miei test testano l'editor con servizi derisi dal resto della suite e con interfaccia utente derisa. Che tipo di test sarebbe?
Yggdrasil,

1
Dipende da ciò che stai testando: stai testando le funzionalità aziendali (test di accettazione)? Stai testando l'integrazione (test di integrazione)? Stai testando cosa succede quando fai clic su un pulsante (test comportamentali)? Stai testando algoritmi (unit test)?
Michael,

4
"Non mi piace concentrarmi sugli approcci del libro di testo - piuttosto, concentrarsi su ciò che dà valore ai tuoi test" Oh, così vero! La prima domanda da porsi è sempre "quale problema risolvo facendo questo?". E progetti diversi potrebbero avere problemi diversi da risolvere!
Laurent Bourgault-Roy,

40

TL; DR: Finché soddisfa le tue esigenze, sì.

Faccio lo sviluppo dello sviluppo guidato dai test di accettazione (ATDD) da molti anni. Può avere molto successo. Ci sono alcune cose di cui essere consapevoli.

  • I test unitari aiutano davvero a rafforzare il COI. Senza test unitari, gli sviluppatori devono assicurarsi che soddisfino i requisiti di un codice ben scritto (in quanto i test unitari guidano un codice ben scritto)
  • Possono essere più lenti e avere falsi fallimenti se in realtà stai usando risorse che sono tipicamente derise.
  • Il test non individua il problema specifico come farebbero i test unitari. È necessario effettuare ulteriori indagini per correggere gli errori di test.

Ora i vantaggi

  • Copertura del test molto migliore, copre i punti di integrazione.
  • Garantisce che il sistema nel suo insieme soddisfi i criteri di accettazione, che è il punto centrale dello sviluppo del software.
  • Rende i refactor di grandi dimensioni molto più facili, più veloci ed economici.

Come sempre spetta a te fare l'analisi e capire se questa pratica è appropriata per la tua situazione. A differenza di molte persone, non penso che esista una risposta giusta idealizzata. Dipenderà dalle tue esigenze e esigenze.


8
Punto eccellente. È fin troppo facile diventare un po 'cadettista spaziale sui test e scrivere centinaia di casi per ottenere un caldo bagliore di soddisfazione quando "passa" a pieni voti. In poche parole: se il tuo software non fa ciò che deve fare dal punto di vista dell'UTENTE, hai fallito il primo e più importante test.
Robbie Dee,

Buon punto per individuare il problema specifico. Se ho un enorme requisito, scrivo test di collaudo che testano l'intero sistema e che scrivo test che testano i compiti secondari di alcuni componenti del sistema per raggiungere il requisito. Con questo posso individuare nella maggior parte dei casi il componente in cui si trova il difetto.
Yggdrasil

2
"I test unitari aiutano a rafforzare il COI"?!? Sono sicuro che intendevi DI lì invece di IoC, ma comunque, perché qualcuno dovrebbe voler imporre l'uso di DI? Personalmente, trovo che in pratica DI porti a non oggetti (programmazione in stile procedurale).
Rogério,

Puoi dare il tuo contributo sull'opzione (IMO migliore) di fare ENTRAMBE l'integrazione e il test unitario rispetto all'argomento di fare solo il test di integrazione? La tua risposta qui è buona, ma sembra inquadrare queste cose come mutuamente esclusive, cosa che non credo siano.
starmandeluxe,

@starmandeluxe Non si escludono a vicenda. Piuttosto si tratta del valore che si desidera derivare dai test. Vorrei testare l'unità ovunque il valore superasse il costo di sviluppo / supporto della scrittura dei test unitari. ex. Certamente testerei l'unità della funzione di interesse composto in un'applicazione finanziaria.
dietbuddha,

18

Bene, ho usato l'approccio simile a TDD per consentire ai test / requisiti di evolvere la progettazione del programma. Il problema era che oltre la metà del tempo di sviluppo per quanto riguarda i test di scrittura / refactoring

I test unitari funzionano meglio quando l'interfaccia pubblica dei componenti per cui vengono utilizzati non cambia troppo spesso. Ciò significa che quando i componenti sono già progettati correttamente (ad esempio, seguendo i principi SOLID).

Quindi credere che un buon design si "evolva" semplicemente "lanciando" molti test unitari su un componente è un errore. TDD non è un "insegnante" per un buon design, può solo aiutare un po 'a verificare che alcuni aspetti del design siano buoni (specialmente la testabilità).

Quando i tuoi requisiti cambiano e devi cambiare gli interni di un componente, e questo interromperà il 90% dei tuoi test unitari, quindi dovrai riformattarli molto spesso, quindi il design molto probabilmente non è stato così buono.

Quindi il mio consiglio è: pensa al design dei componenti che hai creato e come puoi renderli più seguendo il principio aperto / chiuso. L'idea di quest'ultimo è quella di assicurarsi che le funzionalità dei componenti possano essere estese in seguito senza modificarle (e quindi non interrompere l'API del componente utilizzata dai test delle unità). Tali componenti possono (e dovrebbero essere) coperti da test unit test e l'esperienza non dovrebbe essere così dolorosa come l'hai descritta.

Quando non è possibile elaborare immediatamente tale progetto, i test di accettazione e integrazione potrebbero essere davvero un inizio migliore.

EDIT: A volte il design dei componenti può andare bene, ma il design dei test unitari può causare problemi . Esempio semplice: si desidera testare il metodo "MyMethod" della classe X e scrivere

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(supponiamo che i valori abbiano un qualche tipo di significato).

Supponiamo inoltre che nel codice di produzione vi sia una sola chiamata a X.MyMethod. Ora, per un nuovo requisito, il metodo "MyMethod" richiede un parametro aggiuntivo (ad esempio, qualcosa di simile context), che non può essere omesso. Senza test unitari, si dovrebbe refactificare il codice chiamante in un solo posto. Con i test unitari, si devono refactoring 500 posti.

Ma la causa qui non è che l'unità si testa da sola, è solo il fatto che la stessa chiamata a "X.MyMethod" si ripete ancora e ancora, non seguendo rigorosamente il principio "Don't Repeat Yourself (DRY). Quindi la soluzione qui è quello di mettere i dati di test e i relativi valori previsti in un elenco ed eseguire le chiamate a "MyMethod" in un ciclo (o, se lo strumento di test supporta i cosiddetti "test di unità di dati", per utilizzare quella funzione). il numero di posti da cambiare nei test unitari quando la firma del metodo cambia in 1 (invece di 500).

Nel tuo caso reale, la situazione potrebbe essere più complessa, ma spero che tu abbia l'idea - quando i tuoi test unitari utilizzano un'API dei componenti per la quale non sai se potrebbe diventare soggetto a modifiche, assicurati di ridurre il numero di chiamate a quell'API al minimo.


"Questo significa che quando i componenti sono già ben progettati.": Sono d'accordo con te, ma come possono essere già progettati se scrivi i test prima di scrivere il codice, e il codice è il design? Almeno questo è come ho capito TDD.
Giorgio,

2
@Giorgio: in realtà, non importa se scrivi i test prima o poi. Progettare significa prendere decisioni sulla responsabilità di un componente, sull'interfaccia pubblica, sulle dipendenze (dirette o iniettate), sul tempo di esecuzione o sul comportamento del tempo di compilazione, sulla mutabilità, sui nomi, sul flusso di dati, sul flusso di controllo, sui livelli ecc. Buono progettare significa anche rinviare alcune decisioni all'ultimo momento possibile. Il test unitario può mostrarti indirettamente se il tuo progetto era ok: se devi riformattare molti di essi in seguito quando cambiano i requisiti, probabilmente non lo era.
Doc Brown,

@Giorgio: un esempio potrebbe chiarire: supponiamo che tu abbia un componente X con un metodo "MyMethod" e 2 parametri. Usando TDD, scrivi X x= new X(); AssertTrue(x.MyMethod(12,"abc"))prima di implementare effettivamente il metodo. Utilizzando la progettazione iniziale, è possibile scrivere class X{ public bool MyMethod(int p, string q){/*...*/}}prima e scrivere i test in un secondo momento. In entrambi i casi, hai preso la stessa decisione di progettazione. Se la decisione è stata buona o cattiva, TDD non te lo dirà.
Doc Brown,

1
Sono d'accordo con te: sono un po 'scettico quando vedo TDD applicato alla cieca con il presupposto che produrrà automaticamente un buon design. Inoltre, a volte TDD si mette in mezzo se il design non è ancora chiaro: sono costretto a testare i dettagli prima di avere una panoramica di ciò che sto facendo. Quindi, se ho capito bene, siamo d'accordo. Penso che (1) i test unitari aiutino a verificare un progetto, ma il design è un'attività separata e (2) TDD non è sempre la soluzione migliore perché è necessario organizzare le idee prima di iniziare a scrivere test e TDD può rallentare Questo.
Giorgio,

1
In breve, i test unitari possono mostrare difetti nella progettazione interna di un componente. L'interfaccia, le condizioni pre e post devono essere note in anticipo, altrimenti non è possibile creare il test unitario. Pertanto, prima di poter scrivere il test unitario, è necessario eseguire la progettazione del componente su ciò che fa il componente. In che modo ciò - il design di livello inferiore, il design dettagliato o il design interno o come si desidera chiamarlo - può avvenire dopo che il test unitario è stato scritto.
Maarten Bodewes,

9

Sì, certo che lo è.

Considera questo:

  • un test unitario è un piccolo test mirato che esercita un piccolo codice. Ne scrivi molti per ottenere una copertura decente del codice, in modo che tutti (o la maggior parte dei bit scomodi) vengano testati.
  • un test di integrazione è un test ampio e ampio che esercita un'ampia superficie del codice. Ne scrivi pochi per ottenere una copertura decente del codice, in modo che tutti (o la maggior parte dei bit scomodi) vengano testati.

Vedi la differenza complessiva ....

Il problema riguarda la copertura del codice, se è possibile ottenere un test completo di tutto il codice utilizzando i test di integrazione / accettazione, non si verifica alcun problema. Il tuo codice è stato testato. Questo è l'obiettivo.

Penso che potresti aver bisogno di confonderli, poiché ogni progetto basato su TDD richiederà alcuni test di integrazione solo per assicurarsi che tutte le unità funzionino davvero bene insieme (so per esperienza che una base di codice testata da unità al 100% non funziona necessariamente quando li metti tutti insieme!)

Il problema dipende davvero dalla facilità di test, debug degli errori e risoluzione degli errori. Alcune persone trovano che i loro test unitari siano molto bravi in ​​questo, sono piccoli e semplici e gli errori sono facili da vedere, ma lo svantaggio è che devi riorganizzare il tuo codice per adattarlo agli strumenti di unit test e scriverne molti. Un test di integrazione è più difficile da scrivere per coprire un sacco di codice e probabilmente dovrai usare tecniche come la registrazione per eseguire il debug di tutti gli errori (anche se, direi che devi farlo comunque, non puoi fallire i test unitari quando sul posto!).

Ad ogni modo, ottieni comunque un codice testato, devi solo decidere quale meccanismo si adatta meglio a te. (Vorrei andare con un po 'di un mix, testare gli algoritmi complessi e integrare test il resto).


7
Non del tutto vero ... Con i test di integrazione è possibile avere due componenti che sono entrambi difettosi, ma i loro bug si annullano nel test di integrazione. Non importa molto fino a quando l'utente finale lo utilizza in un modo in cui viene utilizzato solo uno di questi componenti ...
Michael Shaw,

1
Copertura del codice! = Testato - a parte i bug che si annullano a vicenda, che dire degli scenari a cui non hai mai pensato? I test di integrazione vanno bene per test di percorso felici ma raramente vedo adeguati test di integrazione quando le cose non vanno bene.
Michael,

4
@Ptolemy Penso che la rarità di 2 componenti difettosi che si annullano a vicenda sia molto, molto più bassa di 2 componenti funzionanti che interferiscono tra loro.
gbjbaanb,

2
@Michael allora non hai fatto abbastanza sforzi nei test, ho detto che è più difficile fare buoni test di integrazione in quanto i test devono essere molto più dettagliati. È possibile fornire dati errati in un test di integrazione con la stessa facilità con cui è possibile eseguire un test unitario. Test di integrazione! = Percorso felice. Si tratta di esercitare più codice possibile, ecco perché ci sono strumenti di copertura del codice che mostrano quanto del tuo codice è stato esercitato.
gbjbaanb,

1
@Michael Quando uso correttamente strumenti come Cucumber o SpecFlow, posso creare test di integrazione che testano anche eccezioni e situazioni estreme con la stessa rapidità dei test unitari. Ma sono d'accordo se una classe ha troppe permutazioni preferisco scrivere un unit test per questa classe. Ma questo sarà meno spesso del caso di avere classi con solo una manciata di percorsi.
Yggdrasil,

2

Penso che sia un'idea orribile.

Poiché i test di accettazione e quelli di integrazione toccano parti più ampie del codice per testare un obiettivo specifico, avranno bisogno di più refactoring nel tempo, non di meno. Peggio ancora, poiché coprono ampie sezioni del codice, aumentano il tempo impiegato a rintracciare la causa principale poiché hai un'area più ampia da cui cercare.

No, di solito dovresti scrivere più test unitari a meno che tu non abbia un'app dispari con un'interfaccia utente del 90% o qualcos'altro che è scomodo per test unitari. Il dolore che stai incontrando non proviene dai test unitari, ma dal primo sviluppo del test. In genere, dovresti dedicare solo 1/3 del tuo tempo alla maggior parte dei test di scrittura. Dopotutto, sono lì per servirti, non viceversa.


2
La principale causa che sento contro il TDD è che interrompe il flusso di sviluppo naturale e applica fin dall'inizio la programmazione difensiva. Se un programmatore è già sotto la pressione del tempo, è probabile che voglia semplicemente tagliare il codice e lucidarlo in seguito. Ovviamente, rispettare una scadenza arbitraria con codice buggy è una falsa economia.
Robbie Dee,

2
In effetti, soprattutto perché "lucidarlo più tardi" non sembra mai accadere realmente - ogni recensione che faccio dove uno sviluppatore spinge il "deve uscire, lo faremo più tardi" quando tutti sappiamo, non succederà - tecnico debito = sviluppatori falliti secondo me.
Michael,

3
La risposta ha senso per me, non so perché abbia ottenuto così tanti svantaggi. Per citare Mike Cohn: "I test unitari dovrebbero essere il fondamento di una solida strategia di automazione dei test e come tali rappresentano la parte più ampia della piramide. I test unitari automatizzati sono meravigliosi perché forniscono dati specifici a un programmatore: c'è un bug ed è attivo linea 47 " mountaingoatsoftware.com/blog/…
guillaume31

4
@ guillaume31 Solo perché una volta qualcuno ha detto che sono bravi non significa che siano bravi. Nella mia esperienza, i bug NON vengono rilevati dai test unitari, perché sono stati modificati in anticipo con il nuovo requisito. Inoltre, la maggior parte dei bug sono bug di integrazione. Pertanto ne rilevo la maggior parte con i miei test di integrazione.
Yggdrasil,

2
@Yggdrasil Potrei anche citare Martin Fowler: "Se si verifica un errore in un test di alto livello, non solo si ha un bug nel codice funzionale, si ha anche un test unitario mancante". martinfowler.com/bliki/TestPyramid.html Comunque, se solo i test di integrazione funzionano per te, allora va bene. La mia esperienza è che, se necessario, sono più lenti, forniscono messaggi di errore meno precisi e meno manovrabili (più combinatori) dei test unitari. Inoltre, tendo ad essere meno a prova di futuro quando scrivo test di integrazione - ragionamento su scenari concepiti prima (mis?) Piuttosto che sulla correttezza di un oggetto in sé.
guillaume31,

2

La "vittoria" con TDD è che una volta che i test sono stati scritti, possono essere automatizzati. Il rovescio della medaglia è che può consumare una parte significativa del tempo di sviluppo. Se questo effettivamente rallenta l'intero processo è discutibile. L'argomento è che il test iniziale riduce il numero di errori da correggere alla fine del ciclo di sviluppo.

È qui che entra in gioco BDD poiché i comportamenti possono essere inclusi nel test unitario, quindi il processo è per definizione meno astratto e più tangibile.

Chiaramente, se fosse disponibile una quantità infinita di tempo, faresti il ​​maggior numero possibile di test su varie varietà. Tuttavia, il tempo è generalmente limitato e i test continui sono convenienti solo fino a un certo punto.

Tutto ciò porta alla conclusione che i test che forniscono il massimo valore dovrebbero essere in primo piano nel processo. Questo di per sé non favorisce automaticamente un tipo di test rispetto a un altro - più che ogni caso deve essere preso nel merito.

Se stai scrivendo un widget da riga di comando per uso personale, saresti interessato principalmente ai test unitari. Considerando che un servizio web dice, richiederebbe una notevole quantità di test di integrazione / comportamentale.

Mentre la maggior parte dei tipi di test si concentra su quella che potrebbe essere definita la "linea da corsa", ovvero testare ciò che è richiesto oggi dal business, i test unitari sono eccellenti per eliminare i bug sottili che potrebbero emergere nelle successive fasi di sviluppo. Poiché questo è un vantaggio che non può essere facilmente misurato, viene spesso trascurato.


1
Scrivo i miei test in anticipo e scrivo abbastanza test per coprire la maggior parte degli errori. Per quanto riguarda i bug che affiorano in seguito. Mi sembra che un requisito sia cambiato o che un nuovo requisito entri in gioco. Quindi i test di integrazione / comportamento devono essere modificati, aggiunti. Se poi viene visualizzato un bug in un vecchio requisito, il mio test per questo lo mostrerà. Per quanto riguarda l'automazione. tutti i miei test vengono eseguiti continuamente.
Yggdrasil,

L'esempio che stavo pensando nell'ultimo paragrafo è dove, per esempio, una libreria è stata utilizzata esclusivamente da una singola applicazione, ma c'era quindi un requisito aziendale per rendere questa una libreria per scopi generici. In questo caso potrebbe essere utile eseguire almeno alcuni test unitari anziché scrivere nuovi test di integrazione / comportamento per ogni sistema collegato alla libreria.
Robbie Dee,

2
I test automatizzati e i test unitari sono una questione completamente ortogonale. Qualsiasi progetto che si rispetti avrà integrazione automatizzata e test funzionali. Certo, non si vedono spesso test unitari manuali, ma possono esistere (fondamentalmente test unitari manuali è un'utilità di test per alcune funzionalità specifiche).
Jan Hudec,

Bene davvero. C'è stato un fiorente mercato per strumenti di terze parti automatizzati che esistono al di fuori della sfera di sviluppo per un tempo considerevole.
Robbie Dee,

1

L'ultimo vantaggio dei test unitari è che sono più veloci. Ho scritto abbastanza test di integrazione per sapere che possono essere quasi veloci quanto i test unitari.

Questo è il punto chiave e non solo "l'ultimo vantaggio". Quando il progetto diventa sempre più grande, i test di accettazione dell'integrazione stanno diventando sempre più lenti. E qui, intendo così lentamente che smetterai di eseguirli.

Naturalmente, anche i test unitari stanno diventando più lenti, ma sono comunque più veloci dell'ordine di grandezza. Ad esempio, nel mio precedente progetto (c ++, circa 600 kLOC, 4000 unit test e 200 test di integrazione), ci sono voluti circa un minuto per eseguire tutti e più di 15 per eseguire test di integrazione. Per costruire ed eseguire i test unitari per la parte da modificare, occorrerebbero in media meno di 30 secondi. Quando puoi farlo così in fretta, ti consigliamo di farlo sempre.

Giusto per chiarire: non dico di non aggiungere test di integrazione e accettazione, ma sembra che tu abbia fatto TDD / BDD in modo sbagliato.

Anche i test unitari sono validi per il design.

Sì, progettare pensando alla testabilità renderà il progetto migliore.

Il problema era che oltre la metà del tempo di sviluppo per quanto riguarda i test di scrittura / refactoring. Quindi alla fine non volevo implementare altre funzionalità perché avrei dovuto refactoring e scrivere a molti test.

Bene, quando i requisiti cambiano, devi cambiare il codice. Direi che non hai finito il tuo lavoro se non hai scritto test unitari. Ma questo non significa che dovresti avere una copertura del 100% con i test unitari, non è questo l'obiettivo. Alcune cose (come la GUI o l'accesso a un file, ...) non sono nemmeno pensate per essere testate in unità.

Il risultato è una migliore qualità del codice e un altro livello di test. Direi che ne vale la pena.


Avevamo anche diversi test di accettazione per 1000 e ci sarebbe voluta un'intera settimana per eseguire tutti.


1
Hai visto il mio esempio? Questo è successo tutto il tempo. Il punto è che, quando implemento una nuova funzionalità, modifico / aggiungo il test unitario in modo che testino la nuova funzionalità, quindi nessun test unitario verrà interrotto. Nella maggior parte dei casi ho effetti collaterali dei cambiamenti che non vengono rilevati dai test unitari, perché l'ambiente è deriso. Nella mia esperienza a causa di questo nessun test unitario mi ha mai detto che ho rotto una funzione esistente. Sono sempre stati i test di integrazione e accettazione che mi hanno mostrato i miei errori.
Yggdrasil,

Per quanto riguarda i tempi di esecuzione. Con un'applicazione in crescita ho principalmente un numero crescente di componenti isolati. Altrimenti ho fatto qualcosa di sbagliato. Quando implemento una nuova funzionalità, è principalmente solo in un numero limitato di componenti. Scrivo uno o più test di accettazione nell'ambito dell'intera applicazione, che potrebbe essere lento. Inoltre scrivo gli stessi test dal punto di vista dei componenti: questi test sono veloci, perché i componenti sono solitamente veloci. Posso eseguire i test dei componenti in ogni momento, perché sono abbastanza veloci.
Yggdrasil,

@Yggdrasil Come ho detto, i test unitari non sono tutti potenti, ma di solito sono il primo livello di test, poiché sono i più veloci. Anche altri test sono utili e dovrebbero essere combinati.
BЈовић,

1
Solo perché sono più veloci non significa che dovrebbero essere usati solo per questo o perché è comune scriverli. Come ho detto, i miei test unitari non si rompono, quindi non hanno alcun valore per me.
Yggdrasil,

1
La domanda era: quale valore ho dal test unitario, quando non si rompono? Perché preoccuparsi di scriverli, quando devo sempre adattarli alle nuove esigenze? L'unico valore che vedo è per algoritmi e altre classi con un'alta permutazione. Ma questi sono meno dei componenti e dei test di accettazione.
Yggdrasil,
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.