JavaRush /Java Blog /Random-IT /Cos'è il TDD e il test unitario [traduzione]
Dr-John Zoidberg
Livello 41
Марс

Cos'è il TDD e il test unitario [traduzione]

Pubblicato nel gruppo Random-IT
Questo articolo è un adattamento di un capitolo del libro The Complete Software Career Guide. Il suo autore, John Sonmez, lo scrive e pubblica alcuni capitoli sul suo sito.
Cos'è il TDD e il test unitario [traduzione] - 1

Un breve glossario per principianti

Il test unitario o test unitario è un processo di programmazione che consente di verificare la correttezza dei singoli moduli del codice sorgente di un programma. L'idea è di scrivere test per ogni funzione o metodo non banale. Test di regressione è un nome generale per tutti i tipi di test del software volti a rilevare errori in aree già testate del codice sorgente. Tali errori - quando, dopo aver apportato modifiche al programma, qualcosa che dovrebbe continuare a funzionare smette di funzionare - sono chiamati errori di regressione. Risultato rosso, fallimento - fallimento del test. La differenza tra il risultato atteso e quello effettivo. Risultato verde, superato : risultato positivo del test. Il risultato reale non è diverso da quello ottenuto. ***
Cos'è il TDD e il test unitario [traduzione] - 2
Ho un rapporto molto misto con il Test Driven Development (TDD) e i test unitari, passando dall'amore all'odio e viceversa. Ero un fan accanito e allo stesso tempo uno scettico sospettoso sull’uso di questa e di altre “migliori pratiche”. Il motivo del mio atteggiamento si basa sul fatto che è emerso un problema serio nei processi di sviluppo software: gli sviluppatori, e talvolta i manager, utilizzano determinati strumenti e metodologie solo perché appartengono alle “best practices”. La vera ragione del loro utilizzo non è ancora chiara. Un giorno ho iniziato a lavorare su un determinato progetto e nel frattempo sono stato informato che avremmo modificato il codice coperto da un numero enorme di test unitari. Non è uno scherzo, erano circa 3000. Di solito questo è un buon segno, un segnale che gli sviluppatori stanno utilizzando metodologie avanzate. Il codice con questo approccio è spesso strutturato e si basa su un'architettura ben congegnata. In una parola, la presenza dei test mi rendeva felice, se non altro perché significava facilitare il mio lavoro di mentore di programmatori. Dato che avevamo già test unitari, tutto quello che dovevo fare era connettere il team di sviluppo per supportarlo e iniziare a scrivere il nostro codice. Ho aperto l'IDE (ambiente di sviluppo integrato) e ho caricato il progetto.
Cos'è il TDD e il test unitario [traduzione] - 3
È stato un grande progetto! Ho trovato una cartella denominata "unit test". "Fantastico", ho pensato. - Lanciamolo e vediamo cosa succede. Ci sono voluti solo pochi minuti e, con mia sorpresa, tutti i test sono stati superati, tutto era verde ( "verde" è un risultato positivo del test. Segnala che il codice funziona come previsto. Il rosso indica "fallimento" o fallimento, quindi c'è un caso in cui il codice non funziona correttamente - nota del traduttore ). Tutti hanno superato la prova. In quel momento lo scettico che è in me si è svegliato. Come mai, tremila test unitari, e li hanno fatti tutti in una volta - e hanno dato un risultato positivo? Nella mia lunga pratica, non riuscivo a ricordare un momento in cui ho iniziato a lavorare su un progetto senza un singolo unit test negativo nel codice. Cosa fare? Controlla manualmente! ChY ha scelto un test casuale, non il più rivelatore, ma è stato subito chiaro cosa stava controllando. Ma mentre lo elaboravo, ho notato una cosa assurda: il test non conteneva alcun confronto con il risultato atteso (asserisce)! Cioè in realtà non è stato controllato proprio nulla ! C'erano alcuni passaggi del test, sono stati eseguiti, ma alla fine del test, dove avrebbe dovuto confrontare i risultati effettivi e quelli attesi, non c'è stato alcun controllo. Il "test" non ha testato nulla. Ho aperto un altro test. Ancora meglio: l'operatore di confronto con il risultato è stato commentato. Brillantemente! Questo è un ottimo modo per eseguire un test, basta commentare il codice che ne causa il fallimento. Ho controllato un altro test, poi un altro... Nessuno di loro ha controllato nulla. Tremila test e tutti completamente inutili. Esiste un'enorme differenza tra scrivere unit test e comprendere unit test e test-driven development (TDD).

Cos'è il test unitario?

Cos'è il TDD e il test unitario [traduzione] - 4
L'idea di base dello unit test è scrivere test che testino la più piccola "unità" di codice. Gli unit test sono generalmente scritti nello stesso linguaggio di programmazione del codice sorgente dell'applicazione. Sono creati direttamente per testare questo codice. Cioè, i test unitari sono codice che verifica la correttezza di altro codice. Uso la parola "test" abbastanza liberamente nel contesto, perché i test unitari in un certo senso non sono test. Non sperimentano nulla. Ciò che intendo è che quando esegui un test unitario di solito non scopri che parte del codice non funziona. Questo lo scopri mentre scrivi il test, perché cambierai il codice finché il test non diventa verde. Sì, il codice potrebbe cambiare in seguito e quindi il test potrebbe fallire. Quindi, in questo senso, un test unitario è un test di regressione. Uno unit test non è come un normale test in cui hai alcuni passaggi da seguire e vedi se il software funziona correttamente o meno. Durante il processo di scrittura di uno unit test, scopri se il codice fa quello che dovrebbe fare o meno e modificherai il codice finché il test non verrà superato.
Cos'è il TDD e il test unitario [traduzione] - 5
Perché non scrivere un test unitario e verificare se viene superato? Se ci pensi in questo modo, i test unitari si trasformano in una sorta di requisiti assoluti per determinati moduli di codice a un livello molto basso. Puoi pensare a un test unitario come a una specifica assoluta . Un test unitario determina che in queste condizioni, con questo particolare set di input, c'è un output che dovresti ottenere da questa unità di codice. Il vero test unitario identifica la più piccola unità coerente di codice, che nella maggior parte dei linguaggi di programmazione - almeno quelli orientati agli oggetti - è una classe.

Ciò che a volte viene chiamato test unitario?

Cos'è il TDD e il test unitario [traduzione] - 6
Il test unitario viene spesso confuso con il test di integrazione. Alcuni "test unitari" testano più di una classe o testano grandi unità di codice. Molti sviluppatori affermano di scrivere unit test quando in realtà scrivono test whitebox di basso livello. Non discutere con questi ragazzi. Sappi solo che in realtà scrivono test di integrazione e che i veri test unitari testano la più piccola unità di codice isolandola da altre parti. Un'altra cosa che viene spesso chiamata test unitario sono i test unitari senza verifica rispetto a un valore previsto. In altre parole, test unitari che in realtà non testano nulla. Qualsiasi test, unitario o meno, deve includere un qualche tipo di verifica: noi lo chiamiamo controllo del risultato effettivo rispetto al risultato atteso. È questa riconciliazione che determina se il test passa o fallisce. Un test che passa sempre è inutile. Un test che fallisce sempre è inutile.

Il valore del test unitario

Perché sono un appassionato di test unitari? Perché è dannoso chiamare test generalizzato, che prevede il test non del blocco più piccolo isolato da altro codice, ma di un pezzo di codice più grande, “test unitario”? Qual è il problema se alcuni dei miei test non confrontano i risultati ricevuti e quelli attesi? Almeno eseguono il codice. Cercherò di spiegare.
Cos'è il TDD e il test unitario [traduzione] - 7
Esistono due ragioni principali per eseguire i test unitari. Il primo è migliorare la progettazione del codice. Ricordi come ho detto che i test unitari non sono realmente test? Quando scrivi unit test corretti, ti costringi a isolare l'unità più piccola di codice. Questi tentativi ti porteranno a scoprire problemi nella struttura del codice stesso. Potresti trovare molto difficile isolare la classe test e non includere le sue dipendenze, e questo potrebbe farti capire che il tuo codice è troppo strettamente accoppiato. Potresti scoprire che la funzionalità principale che stai tentando di testare si estende su più moduli, portandoti a credere che il tuo codice non sia sufficientemente coerente. Quando ti siedi per scrivere un test unitario, potresti improvvisamente scoprire (e credimi, succede!) che non hai idea di cosa dovrebbe fare il codice. Di conseguenza, non sarai in grado di scrivere un test unitario per questo. E, naturalmente, potresti trovare un vero bug nell'implementazione del codice, poiché lo unit test ti costringe a pensare fuori dagli schemi e testare diversi set di input che potresti non aver considerato.
Cos'è il TDD e il test unitario [traduzione] - 8
Se aderisci rigorosamente alla regola di "testare la più piccola unità di codice isolatamente dalle altre" quando crei unit test, sei destinato a trovare ogni sorta di problema con quel codice e la progettazione di quei moduli. Nel ciclo di vita dello sviluppo del software, il test unitario è più un'attività di valutazione che un'attività di test. Il secondo obiettivo principale dello unit test è creare una serie automatizzata di test di regressione che possano fungere da specifica di basso livello del comportamento del software. Cosa significa? Quando impasti la pasta, non la rompi. Da questo punto di vista gli unit test sono test, più specificatamente test di regressione. Tuttavia, lo scopo dei test unitari non è semplicemente quello di creare test di regressione. In pratica, gli unit test raramente rilevano regressioni, poiché una modifica all'unità di codice che si sta testando contiene quasi sempre modifiche allo unit test stesso. Il test di regressione è molto più efficace a un livello superiore, quando il codice viene testato come una "scatola nera", perché a questo livello la struttura interna del codice può essere modificata, mentre ci si aspetta che il comportamento esterno rimanga lo stesso. I test unitari a loro volta controllano la struttura interna in modo che quando tale struttura cambia, i test unitari non falliscono. Diventano inutilizzabili e ora necessitano di essere cambiati, buttati o riscritti. Ora sai di più sul vero scopo dei test unitari rispetto a molti sviluppatori di software veterani.

Che cos'è il Test Driven Development (TDD)?

Cos'è il TDD e il test unitario [traduzione] - 9
Nel processo di sviluppo del software, una buona specifica vale oro. L'approccio TDD prevede che prima di scrivere qualsiasi codice, si scriva un test che servirà da specifica, ovvero definirà cosa dovrebbe fare il codice. Questo è un concetto di sviluppo software estremamente potente, ma spesso viene utilizzato in modo improprio. In genere, lo sviluppo basato sui test implica l'utilizzo di test unitari per guidare la creazione del codice dell'applicazione. Ma in realtà questo approccio può essere applicato a qualsiasi livello. Tuttavia, in questo articolo supporremo di utilizzare unit test per la nostra applicazione. L'approccio TDD capovolge tutto e invece di scrivere prima il codice e poi scrivere unit test per testarlo, scrivi prima un unit test e poi scrivi il codice per rendere quel test verde. In questo modo, il test unitario “guida” lo sviluppo del codice. Questo processo viene ripetuto più e più volte. Scrivi un altro test che definisce più funzionalità di ciò che il codice dovrebbe fare. Quindi scrivi e modifichi il codice per assicurarti che il test venga completato con successo. Una volta ottenuto un risultato verde, inizi a refactoring del codice, ovvero a refactoring o ripulirlo per renderlo più conciso. Questa catena di processi viene spesso chiamata "Red-Green-Refactoring" perché prima lo unit test fallisce (rosso), poi il codice viene scritto per adattarsi al test, assicurandosi che abbia successo (verde), e infine il codice viene ottimizzato ( refactoring). .

Qual è l'obiettivo del TDD?

Cos'è il TDD e il test unitario [traduzione] - 10
Lo sviluppo basato sui test (TDD), come i test unitari, può essere utilizzato in modo errato. È molto facile chiamare ciò che fai "TDD" e persino seguire la pratica senza capire perché lo stai facendo in quel modo. Il valore più grande del TDD è che i test vengono eseguiti per produrre specifiche di qualità. TDD è essenzialmente la pratica di scrivere specifiche precise che possono essere controllate automaticamente prima che venga scritta la codifica. I test sono le migliori specifiche perché non mentono. Non ti diranno dopo due settimane di tormento con il codice "non è affatto quello che intendevo". I test, se scritti correttamente, passano o falliscono. I test indicano chiaramente cosa dovrebbe accadere in determinate circostanze. Pertanto, l'obiettivo di TDD è fornirci una comprensione completa di ciò che dobbiamo implementare prima di iniziare a implementarlo. Se stai iniziando con TDD e non riesci a capire cosa dovrebbe testare il test, allora devi porre più domande. Un altro ruolo importante di TDD è preservare e ottimizzare il codice. La manutenzione del codice è costosa. Scherzo spesso dicendo che il miglior programmatore è quello che scrive il codice più breve che risolve qualche problema. O anche colui che dimostra che questo problema non ha bisogno di essere risolto, e quindi rimuove completamente il codice, poiché è stato questo programmatore a trovare il modo giusto per ridurre il numero di errori e ridurre i costi di manutenzione dell'applicazione. Usando TDD, puoi essere assolutamente sicuro di non scrivere codice non necessario, poiché scriverai solo codice per superare i test. Esiste un principio di sviluppo del software chiamato YAGNI (non ne avrai bisogno). Il TDD impedisce YAGNI.

Tipico flusso di lavoro di sviluppo basato su test (TDD).

Cos'è il TDD e il test unitario [traduzione] - 11
Comprendere il significato di TDD da un punto di vista puramente accademico è difficile. Quindi diamo un'occhiata ad un esempio di una sessione TDD. Immagina di sederti alla tua scrivania e abbozzare rapidamente quello che pensi sarà un progetto di alto livello per una funzionalità che consente a un utente di accedere a un'applicazione e modificare la propria password se la dimentica. Decidi di iniziare con la prima implementazione della funzione login, creando una classe che gestirà tutta la logica del processo di login. Apri il tuo editor preferito e crei uno unit test chiamato "L'accesso vuoto impedisce all'utente di accedere". Scrivi il codice unit test che crea prima un'istanza della classe Login (che non hai ancora creato). Quindi scrivi il codice per chiamare un metodo nella classe Login che passa un nome utente e una password vuoti. Infine, scrivi un controllo rispetto al risultato atteso, controllando che l'utente con un login vuoto non abbia effettivamente effettuato l'accesso. Stai provando a eseguire un test, ma non viene nemmeno compilato perché non hai una classe Login. Risolvi questa situazione e crei una classe Login insieme a un metodo in quella classe per accedere e un altro per controllare lo stato dell'utente per vedere se ha effettuato l'accesso. Finora non hai implementato la funzionalità di questa classe e il metodo di cui abbiamo bisogno. A questo punto esegui il test. Ora viene compilato, ma fallisce immediatamente.
Cos'è il TDD e il test unitario [traduzione] - 12
Ora torni al codice e implementi la funzionalità per superare il test. Nel nostro caso, ciò significa che dovremmo ottenere il risultato: “l’utente non ha effettuato l’accesso”. Esegui nuovamente il test e ora lo supera. Passiamo alla prova successiva. Ora immaginiamo che tu debba scrivere un test chiamato "L'utente ha effettuato l'accesso se ha inserito un nome utente e una password validi". Scrivi uno unit test che crea un'istanza della classe Login e tenta di accedere con un nome utente e una password. In uno unit test, scrivi un'istruzione secondo cui la classe Login dovrebbe rispondere sì alla domanda se l'utente ha effettuato l'accesso. Esegui questo nuovo test e ovviamente fallisce perché la tua classe Login restituisce sempre che l'utente non ha effettuato l'accesso. Ritorni alla classe Login e implementi del codice per verificare che l'utente abbia effettuato l'accesso. In questo caso, dovrai capire come isolare questo modulo. Per ora, il modo più semplice per farlo è codificare il nome utente e la password utilizzati nel test e, se corrispondono, restituire il risultato "l'utente ha effettuato l'accesso". Apporta questa modifica, esegui entrambi i test ed entrambi vengono superati. Passiamo all'ultimo passaggio: guardi il codice generato e cerchi un modo per riorganizzarlo e semplificarlo. Quindi l'algoritmo TDD è:
  1. Creato un test.
  2. Abbiamo scritto il codice per questo test.
  3. Ho rifattorizzato il codice.

conclusioni

Cos'è il TDD e il test unitario [traduzione] - 13
Questo è tutto ciò che volevo dirti sui test unitari e sul TDD in questa fase. In effetti, ci sono molte difficoltà associate al tentativo di isolare i moduli di codice, poiché il codice può essere molto complesso e confuso. Pochissime classi esistono in completo isolamento. Invece, hanno dipendenze, e quelle dipendenze hanno dipendenze e così via. Per affrontare tali situazioni, il veterano del TDD utilizza dei mock, che aiutano a isolare le singole classi sostituendo gli oggetti nei moduli dipendenti. Questo articolo è solo una panoramica e un'introduzione in qualche modo semplificata ai test unitari e al TDD, non entreremo nei dettagli sui moduli fittizi e sulle altre tecniche TDD. L'idea è di fornirti i concetti e i principi di base del TDD e dei test unitari che, si spera, tu ora possiedi. Originale - https://simpleprogrammer.com/2017/01/30/tdd-unit-testing/
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION