Test Driven Development. Questo articolo sullo sviluppo guidato dai test (TDD) aiuterà a familiarizzare con questo ciclo di sviluppo e ad adattarlo ai proprio metodi di codifica.
Il concetto di sviluppo guidato dai test (TDD) è stato introdotto nel 2003 da Kent Beck. Non esiste una definizione formale, ma Beck fornisce approcci ed esempi di TDD. L’obiettivo del TDD è “scrivere codice pulito che funzioni”.
Nel TDD si segue una sola regola empirica: “Modificare il codice di produzione solo se un test fallisce. In caso contrario, si deve solo rifattorizzare per ottimizzare il codice. Per i requisiti aggiornati, convertirli in casi di test, aggiungere i test e scrivere il nuovo codice solo successivamente”.
Il TDD è un ciclo di sviluppo molto breve e ripetitivo. I requisiti del cliente vengono trasformati in casi di test altamente specifici e il software viene scritto e migliorato per superare i nuovi test.
Lo sviluppo guidato dai test è correlato ai concetti di programmazione test-first della programmazione estrema, che sostiene aggiornamenti/rilasci frequenti del software in cicli di sviluppo brevi e promuove ampie revisioni del codice, test unitari e l’aggiunta incrementale di funzionalità.
Un concetto strettamente correlato al TDD è l’Acceptance Test-Driven Development (ATDD), in cui il cliente, lo sviluppatore e il tester partecipano tutti al processo di analisi dei requisiti. Il TDD è adatto agli sviluppatori di applicazioni mobili e web, mentre l’ATDD è uno strumento di comunicazione per garantire che i requisiti siano ben definiti.
Partiamo dalle basi e diamo un’occhiata al ciclo TDD, noto anche come processo Red-Green-Refactor, passo dopo passo.
Il ciclo di sviluppo guidato dai test:
- Aggiungere un test, che sicuramente fallirà. (Rosso)
Nel TDD, ogni caratteristica di un software viene prima aggiunta in termini di casi di test. Per ogni funzione nuova o aggiornata viene creato un test. Per scrivere i test, gli sviluppatori devono comprendere le specifiche e i requisiti della funzione.
Questa pratica separa il TDD dai metodi tradizionali di sviluppo del software, in cui i test unitari vengono scritti dopo la stesura del codice sorgente. In questo modo, il TDD fa sì che lo sviluppatore si concentri sui requisiti prima di scrivere il codice.
- Eseguire tutti i test. Verificare se qualche test fallisce.
L’esecuzione dei test convalida il corretto funzionamento del test harness e contemporaneamente dimostra che, poiché i nuovi test aggiunti falliscono con il codice esistente, è necessario un nuovo codice. - Scrivere solo il codice sufficiente a superare tutti i test. (Verde)
Il nuovo codice scritto in questa fase può non essere perfetto e può superare i test in modo irrilevante. L’unico requisito in questa fase è che tutti i test vengano superati. Un modo possibile per iniziare ad aggiungere le dichiarazioni è quello di restituire una costante e aggiungere gradualmente blocchi logici per costruire la funzione. - Eseguire tutti i test. Se qualche test fallisce, tornare al punto 3. Altrimenti, continuare.
Se tutti i test vengono superati, si può affermare che il codice soddisfa i requisiti del test e che non compromette alcuna funzionalità esistente. Se un test fallisce, il codice deve essere modificato per garantire il superamento di tutti i test. - Riformulare il codice. (Refactor)
Man mano che il codice base cresce, deve essere ripulito e mantenuto regolarmente. Come? Ci sono alcuni modi:
Il nuovo codice che potrebbe essere stato aggiunto per comodità per superare un test può essere spostato al suo posto logico nel codice.
Le duplicazioni devono essere eliminate.
Le definizioni e i nomi degli oggetti devono essere impostati in modo da rappresentare il loro scopo e il loro utilizzo.
Con l’aggiunta di altre caratteristiche, le funzioni diventano lunghe. Può essere utile dividerle e assegnare loro nomi accurati per migliorare la leggibilità e la manutenibilità.
Poiché tutti i test vengono rieseguiti durante la fase di refactoring, lo sviluppatore può essere sicuro che il processo non altera alcuna funzionalità esistente.
- Se viene aggiunto un nuovo test, ripetere il procedimento dal punto 1.
Procedere a piccoli passi, puntando a un numero di modifiche compreso tra 1 e 10 tra ogni esecuzione di test.
Se il nuovo codice non soddisfa rapidamente un nuovo test o se altri test non correlati falliscono inaspettatamente, annullare/ritornare al codice funzionante, invece di eseguire un debug approfondito.
Quando si usano librerie esterne, è importante non fare incrementi così piccoli da testare semplicemente la libreria stessa, a meno che non si tratti di verificare se la libreria è obsoleta/incompatibile, buggata o non completa.
Pratiche comuni nel ciclo TDD
Di seguito forniamo una panoramica delle pratiche comuni del TDD, che aiuteranno a scrivere al meglio in nostro codice.
Piccole unità
Un’unità è una classe/modulo costituita da un gruppo di funzioni strettamente correlate, spesso chiamato modulo. Mantenere le unità di dimensioni ridotte comporta dei vantaggi, come una maggiore facilità di test e di debug.
Struttura dei test
Poiché si eseguiranno sempre dei test per le unità, è importante applicare la seguente struttura di test:
- Impostazione: Portare il sistema o l’unità in prova (UUT) nello stato necessario per eseguire i test, assicurando la preparazione del sistema per il test.
- Esecuzione: Eseguire il test sul target e monitorare tutti i valori di ritorno e le uscite, assicurandosi che il percorso di esecuzione sia quello desiderato.
- Convalida: Asserire/assicurare che i risultati siano corretti. È il momento di dichiarare se il test è passato/fallito.
- Pulizia: Ripristinare il sistema di test allo stato originale. Ciò consente l’esecuzione immediata di un altro test.
Pratiche da evitare
Determinismo: assicurarsi che i test siano deterministici. La dipendenza da eventi come le chiamate API o la data/ora del sistema può far fallire i test anche senza modifiche al codice.
Se possibile, evitare di richiedere un ordine di esecuzione per i test e consentire l’esecuzione casuale dei test. Allo stesso modo, evitare che i test dipendano da risultati precedenti o da altri test.
Test su tempi o prestazioni precise del comportamento di esecuzione.
Non sviluppare casi di test che valutano più di quanto dovrebbero.
Non progettare test che richiedono tempi di esecuzione significativamente più lunghi.
Pratiche individuali
Mantenere ogni test focalizzato solo sui risultati necessari per la sua convalida.
Nei sistemi non in tempo reale, sviluppare test legati al tempo per consentire una tolleranza di esecuzione. È prassi comune lasciare un margine dal 5% al 10% per l’esecuzione tardiva, per ridurre la probabilità di falsi negativi durante i test.
Trattare il codice di test come quello di produzione. Questo migliora la qualità e la robustezza del codice.
Se possibile, dividere i test in test più piccoli.
Come team, rivedere i propri test e le proprie pratiche di test per condividere le tecniche più efficaci e per individuare le cattive abitudini.
Pratiche avanzate
L’Acceptance Test Driven Development (ATDD) prevede pratiche TDD avanzate e il team di sviluppo ha l’obiettivo di soddisfare i test di accettazione definiti dal cliente. Il cliente può disporre di un meccanismo automatizzato per decidere se il software soddisfa i suoi requisiti.
Test-Driven Development (TDD) – Vantaggi e svantaggi
Benefici
Scrivere test in TDD ti costringe a pensare ai casi d’uso e migliora la produttività.
Anche considerando la quantità di codice basata sulla scrittura di unit test, l’implementazione totale sarà più breve e con meno di bug (secondo un modello sviluppato da Müller e Padberg, “About the Return on Investment of Test-Driven Development”).
Il debug diventa più semplice.
Se vengono seguite le pratiche TDD comuni, il codice sviluppato è modularizzato, flessibile ed estensibile.
Rilevamento automatico della regressione su ogni aggiornamento incrementale.
I test automatizzati sono molto approfonditi. Poiché non viene scritto più codice del necessario per superare i test falliti, questi test automatizzati tendono a coprire ogni percorso di codice.
Documentazione più semplice, i test unitari sono auto-documentati, più facili da leggere e comprendere. Si deve sempre documentare in modo descrittivo il codice sorgente/produzione.
Limitazioni
TDD non funziona bene quando sono richiesti test funzionali, come la progettazione della GUI.
Quando gli sviluppatori stessi scrivono gli unit test, i test possono condividere gli stessi punti ciechi del codice.
A volte, un numero elevato di test superati può creare un falso senso di sicurezza, causando un minor numero di attività di test durante i test di integrazione, causando potenzialmente problemi.
I test diventano parte delle spese generali di manutenzione. Test scritti male possono causare ulteriori costi di manutenzione o aggiornamento.
Il livello di dettaglio raggiunto durante il TDD non può essere facilmente ricreato in seguito.
Sviluppo basato sui test nella pratica
Per sistemi di grandi dimensioni
Per i sistemi di grandi dimensioni, i test sono impegnativi e richiedono un’architettura modulare con componenti ben definiti. Alcuni requisiti chiave che devono essere soddisfatti sono:
- L’elevata coesione garantisce che ciascun modulo fornisca una serie di funzionalità correlate, rendendo più semplice la manutenzione dei test corrispondenti.
- Il basso accoppiamento consente il test isolato dei moduli.
Modellazione degli scenari
Nello Scenario Modeling viene costruita una serie di grafici di sequenza, ciascuno dei quali si concentra su un singolo scenario di esecuzione a livello di sistema. Fornisce un veicolo eccellente per creare strategie di interazione in risposta a un input.
Ciascun modello di scenario funge da insieme di requisiti per le funzionalità fornite da un componente. La modellazione degli scenari può essere utile nella costruzione di test TDD in sistemi complessi.
Visibilità e sicurezza del codice
È importante differenziare il codice tra test e produzione. La suite di unit test deve essere in grado di accedere al codice da testare. Tuttavia, la progettazione di criteri quali l’occultamento e l’incapsulamento delle informazioni e la separazione dei moduli non deve essere compromessa.
Nella progettazione orientata agli oggetti, i test non saranno ancora in grado di accedere a dati e metodi privati e richiederanno una codifica aggiuntiva. In alternativa, è possibile utilizzare una classe interna all’interno del codice sorgente per contenere gli unit test. Tali hack di test non dovrebbero rimanere nel codice di produzione. I professionisti del TDD spesso discutono se i dati privati debbano essere testati.
Conclusione
In questo articolo abbiamo sintetizzato il Test-Driven Development (TDD). Abbiamo visto i vantaggi e i limiti del TDD e le pratiche associate al TDD, coprendo le idee necessarie da conoscere per iniziare ad adottare il ciclo TDD.
(fonte)
Innovaformazione, scuola informatica specialistica promuove le metodologie di sviluppo software moderne e DevOps. Legate ai concetti di TDD abbiamo dei corsi sull’Automation Testing visonabile al seguente LINK ed il Corso DevSecOps. Corsi per aziende.
INFO: info@innovaformazione.net – tel. 3471012275 (Dario Carrassi)
Articoli correlati
Claude Code e Migrazioni SAP
Claude Code controllo remoto
Opportunità Carriera Contabilità SAP
Guida SIA AI
Guida Dual LLM Verification
