Unit testing
# Unit testing

In ingegneria del software, per unit testing, test unitario o collaudo unitario, si intende l'attività di collaudo di singole unità di un software. Per unità si intende normalmente il minimo componente di un programma dotato di funzionamento autonomo; a seconda del paradigma di programmazione o linguaggio di programmazione, questo può corrispondere per esempio a una singola funzione nella programmazione procedurale, o una singola classe o un singolo metodo nella programmazione a oggetti.

Lo unit testing viene normalmente eseguito dagli sviluppatori, e può essere occasionalmente glass box, ovvero essere esplicitamente basato sulla conoscenza dell'architettura e del funzionamento interno di un componente oltre che sulle sue funzionalità esternamente esposte. Lo unit testing è tipicamente automatico, ed è implementato utilizzando librerie predisposte per ciascun linguaggio di programmazione (per esempio JUnit in Java, da cui prendono spunto anche la maggior parte delle altre librerie software di Unit Testing). Lo sviluppo dei test case (cioè delle singole procedure di test) può essere considerato parte integrante dell'attività di sviluppo (per esempio, nel caso dello sviluppo guidato da test) ed è una best practice raccomandata sempre durante lo sviluppo software in quanto è in grado di verificare il buon funzionamento di ogni singola componente del software in poco tempo e con grande affidabilità.

## Vantaggi

Lo scopo dello unit testing è quello di verificare il corretto funzionamento di parti di programma, permettendo così una precoce individuazione dei bug. Uno unit testing accurato può dare una prova certa se un pezzo di codice funziona correttamente, con importanti vantaggi:

### Semplifica le modifiche

Lo unit testing facilita la modifica del codice del modulo in momenti successivi (refactoring) con la sicurezza che il modulo continuerà a funzionare correttamente. Il procedimento consiste nello scrivere test case per tutte le funzioni e i metodi, in modo che se una modifica produce un fallimento del test, si possa facilmente individuare la modifica responsabile.

Unit test già predisposti semplificano la vita al programmatore nel controllare che una porzione di codice stia ancora funzionando correttamente. Un buon unit testing produce test case che coprano tutti i percorsi del codice dell'unità, con particolare attenzione alle condizioni nei cicli (test sugli if, while, for).

In sistemi con unit testing continuo, tali test sono in grado di garantire automaticamente integrità del codice ad ogni modifica.

### Semplifica l'integrazione

Lo unit testing semplifica l'integrazione di moduli diversi perché limita i malfunzionamenti a problemi di interazione tra i moduli e non nei moduli stessi, rendendo i test di integrazione più semplici.

Un argomento molto dibattuto è quello della non necessità di test di integrazione manuali, in caso si sia organizzata una procedura di unit testing sufficientemente completa. In realtà spesso un elaborato sistema di unit testing fornisce una falsa sicurezza e un test di integrazione gestito da esseri umani è in genere ugualmente necessario. Probabilmente la reale necessità del fattore umano nella procedura di test dipende dalle caratteristiche del sistema nel quale si sviluppa e soprattutto dalla disponibilità di risorse.

### Supporta la documentazione

Lo unit testing fornisce una documentazione "viva" del codice, perché è intrinsecamente un esempio di utilizzo dell'API del modulo.

I test case incorporano le caratteristiche critiche per il successo di un'unità di codice. Tali caratteristiche indicano l'uso appropriato dell'unità e i comportamenti errati che devono essere identificati nel suo funzionamento. Pertanto lo unit testing documenta tali caratteristiche, sebbene in molti ambienti questi non possono costituire la sola documentazione necessaria. In compenso, la tradizionale documentazione diventa spesso obsoleta a causa di successive modifiche del codice non documentate.

## Limiti

In generale il testing non riesce ad identificare tutti gli errori in un programma e lo stesso vale per lo Unit Testing che, analizzando per definizione le singole unità, non può identificare gli errori di integrazione, problemi legati alla performance e altri problemi legati al sistema in generale. Lo unit testing è più efficace se utilizzato in congiunzione con altre tecniche di testing del software.

Come ogni forma di testing, anche lo Unit Testing non può certificare l'assenza di errori, ma può solo evidenziarne la presenza.

Il testing del software è un problema di matematica combinatoria. Per esempio, ogni test booleano richiede almeno due test, uno per la condizione di "vero" e uno per quella di "falso". Si può dimostrare che, per ogni linea di codice funzionale, siano necessarie dalle 3 alle 5 linee di codice per il test. È quindi irrealistico testare tutte le possibili combinazioni di input di qualsiasi codice non banale senza un tool apposito di generazione di casi di test.

Per ottenere gli sperati benefici dallo unit test, è richiesto un rigoroso senso di disciplina durante tutto il processo di sviluppo. È essenziale mantenere traccia non solo dei test che sono stati sviluppati ed eseguiti, ma anche di tutte le modifiche effettuate al codice funzionale dell'unità in esame e di tutte le altre. L'uso di un sistema di controllo versione è essenziale. Se una versione successiva di una unità fallisce un test che aveva passato in precedenza, il sistema di controllo versione permette di evidenziare le modifiche al codice intervenute nel frattempo.

## Separazione dell'interfaccia dall'implementazione

Poiché alcune classi possono far riferimento ad altre, il test di una classe spesso si propaga alle altre. Un esempio è una classe che interagisce con una base di dati: testare la classe spesso implica la scrittura del codice che interagisce con il database. Questo è un problema perché lo unit test non dovrebbe mai varcare i confini della classe. La conseguenza è che il programmatore, nel progettare lo unit testing, impara ad isolare la classe da analizzare, individuando l'interfaccia con il database ed implementandola con un mock object, una simulazione dell'oggetto reale che può essere effettuata in condizioni controllate. L'effetto è un test più approfondito e quindi uno unit testing di qualità più elevata.

## Applicazioni

### Extreme Programming

Lo unit testing è la parte fondamentale dell'extreme programming (XP), che si basa su uno unit testing framework, che può essere fornito da terze parti o creato all'interno del gruppo di sviluppo.

L'Extreme Programming usa la creazione di unit test per lo sviluppo guidato da test (TDD, test driven development). Lo sviluppatore scrive uno unit test che evidenzi una funzionalità richiesta dalle specifiche o un difetto possibile. Il test può fallire perché la funzionalità non è stata ancora implementata o perché il difetto cercato è effettivamente verificato. Quindi lo sviluppatore scrive il codice funzionale più semplice possibile affinché il test sia eseguito con successo.

Tutte le classi sono testate in questo modo e lo sviluppatore rilascia il codice di test insieme al codice funzionale da esso testato. La XP, con uno unit testing approfondito, presenta tutti i benefici descritti sopra e i test sono utilizzati successivamente come test di regressione.

Ovviamente questa tecnica è applicabile solo alle unità deterministiche del codice, mentre tutto il codice di integrazione o non deterministico, ad esempio il codice che si basa sulle interazioni utente, le basi dati, l'interazione con il sistema operativo o le periferiche, le connessioni di rete, non può essere testato in questa modalità. In certi casi si usano delle interfacce simulate (mock) per emulare l'interazione con elementi non deterministici (ad esempio simulando un possibile database o il comportamento di un utente) ma per ovvie ragioni questo tipo di testing non è né completo, né totalmente realistico.

### Tecniche

Normalmente lo unit testing è automatizzato, ma può anche essere eseguito manualmente. Non c'è alcuna raccomandazione in proposito da parte dello IEEE. L'approccio manuale può richiedere la documentazione dei passi necessari per l'esecuzione dello unit test. In ogni caso, lo scopo dello unit test è quello di isolare un modulo e certificarne la correttezza. L'automatizzazione, a differenza del test manuale, è un modo efficiente per raggiungere questi obbiettivi e portare i benefici descritti.

Con l'approccio automatico, per realizzare il completo isolamento del modulo da testare, il codice funzionale è testato al di fuori del suo ambiente naturale, in un apposito framework. Questo approccio ha il pregio di evidenziare dipendenze non richieste del modulo in esame dagli altri.

In un framework automatizzato lo sviluppatore codifica i casi da testare in modo che verifichino la correttezza del modulo e durante l'esecuzione venga riportato l'eventuale fallimento di ogni test. In alcuni casi di fallimento di test critici, l'intera procedura di test viene fermata.

La conseguenza dello unit testing è un approccio alla programmazione che favorisce la scrittura di codice in moduli indipendenti ed interoperanti, che a sua volta contribuisce, insieme ai design pattern e ad altre pratiche comuni alla realizzazione del miglior codice possibile.

### Unit testing framework

Esistono framework sviluppati per lo unit testing di una miriade di linguaggi. È generalmente possibile realizzare lo unit testing senza il supporto di uno specifico framework, scrivendo il codice che testi il modulo e che implementi meccanismi quali le asserzioni, le eccezioni o le uscite anticipate per segnalare i fallimenti. Questo approccio è prezioso perché semplifica l'adozione dello unit testing, ma è anche limitato dall'assenza di molte funzionalità avanzate dei framework disponibili.

