ATTENZIONE: il nuovo sito di riferimento per questa guida e altre cose più o meno interessanti è il mio BLOG

Indice
Prossimo capitolo - Organizzare lo sviluppo con Ant

Capitolo 9 - Test delle unità

Un modo ovvio per testare i metodi di una classe è quello di inserire nel codice dei comandi che stampano il valore di determinate variabili, e di controllare se questi valori sono consistenti.
Esiste un metodo automatico per verificare che certe condizioni siano soddisfatte senza "inquinare" il codice con comandi di stampa: usare JUnit.

Una classe da testare

Supponiamo di dover testare una classe con dei metodi che calcolano il quoziente e il resto della divisione intera - naturalmente una classe del genere ha solo un interesse "teorico", allo scopo in java esistono già gli operatori / e %:

	package algebra;

	public class Div
	{
		/** divisione intera per sottrazioni successive */
		public static int div(int a, int b)
		{
			if( a == 0 || a < b )
				return 0;

			return div( a-b, b ) + 1;
		}

		/** resto della divisione intera */
		public static int mod(int a, int b)
		{
			if( a == 0 )
				return 0;

			if( a < b )
				return a;

			return mod( a-b, b );
		}
	}

La classe di test

JUnit 4.0 fa uso delle annotazioni, un meccanismo introdotto in Java 5 che permette appunto di annotare il codice per fornire indicazioni ad altri programmi - ad esempio JUnit.

Una classe di test per la classe Div si potrebbe scrivere così:

	import algebra.*;	// importo la classe Div
	import org.junit.*;

	import static org.junit.Assert.*;	// static import
5
	public class DivTest
	{
		@Test public void divisioni()
		{
10			assertTrue( Div.div(654, 321) == 2 );
			assertTrue( Div.div(3, 2) == 1 );
			assertTrue( Div.div(123456, 15) == 8230 );
			assertTrue( Div.div(0, 741) == 0 );
		}
15
		...
	}
L'importazione della classe algebra.Div potrebbe non essere necessaria.
In linea 4 si trova un'altra novità di java 5, la possibilità di importare i membri statici di una classe; in questo caso si importa il metodo assertTrue della classe Assert, che fa fallire il test nel caso la condizione non sia vera. Gli static import dovrebbero comunque essere usati solo sporadicamente, se effettivamente rendono il codice più leggibile.

In linea 8 è stato annotato con @Test il metodo divisioni, che controlla i risultati di alcune invocazioni del metodo statico div.

Eseguire i test

A questo punto basta eseguire org.junit.runner.JUnitCore, con parametri i nomi delle classi contenenti i metodi annotati come @Test:

	java org.junit.runner.JUnitCore DivTest
che in questo caso scriverà:
	JUnit version 4.0
	.
	Time: 0

	OK (1 test)
Nel classpath deve essere presente il file junit-4.0.jar (in questo caso anche la directory che contiene algebra, per trovare la classe Div).

Per poter eseguire i test scritti con la versione 4 di JUnit con un runner di versioni precedenti, come quelle incluse in Ant, Netbeans, eccetera, è necessario includere in ogni classe di test il seguente metodo "di adattamento":

	public static junit.framework.Test suite()
	{
		return new junit.framework.JUnit4TestAdapter(DivTest.class);
	}
avendo cura di sostituire DivTest.class con il nome della classe di test.

Eccezioni!

Proviamo ad aggiungere un metodo alla classe DivTest:

	...
	@Test public void divisioniPer0()
	{
		Div.div(0, 0);
	}
	...
questo non dovrebbe essere permesso, si tratta di una divisione per 0, eppure il test passa.

Il test che segue invece non passa:

	...
	@Test public void divisioniPer0()
	{
		Div.div(5, 0);
	}
	...
ma solo perché la ricorsione non arriva mai al caso base e quindi lo spazio sullo stack finisce.

In entrambi i casi l'invocazione del metodo dovrebbe lanciare un'eccezione di "divisione per zero". Il meccanismo delle annotazioni permette di specificare dei parametri; in questo caso è possibile indicare a JUnit quale eccezione deve essere lanciata nel corpo del metodo di test:

	...
	@Test (expected = ArithmeticException.class)
		public void divisioniPer0()
	{
		Div.div(0, 0);
	}
	...
Adesso il test divisioniPer0 non passa, visto che non viene lanciata nessuna eccezione. Se nel corpo del metodo si invocasse div(8, 0) il test fallirebbe comunque, visto che si verifica uno StackOverflowError, in luogo di una ArithmeticException.

Per fare in modo che il test passi bisogna modificare il codice di div(...), in modo che lanci l'eccezione se il divisore è zero:

	...
	/** divisione intera per sottrazioni successive */
	public static int div(int a, int b)
	{
		if( b == 0 )
			throw new ArithmeticException("/ by zero");

		if( a == 0 || a < b )
			return 0;
		...
	}
	...
Le stesse considerazioni possono essere fatte per il metodo mod(...).

Timeout

È possibile indicare a JUnit che un test deve considerarsi fallito se trascorre un certo numero di millisecondi - ad esempio per un timeout di 30 secondi si può scrivere:

	@Test (timeout = 30000) ...

Fixtures

JUnit permette di isolare il codice di setup, che deve essere eseguito prima di ogni test della classe - ad esempio per istanziare un oggetto - e il codice di teardown, eseguito dopo ogni test - ad esempio per liberare risorse, chiudere file, eccetera. Questo codice va messo in metodi annotati con @Before e @After, con nomi a piacere:

	...
	@Before public void setUp() { ... }
	@After public void tearDown() { ... }
	...
Quindi, in una classe con due metodi di test, test1 e test2, la sequenza di esecuzione sarà:
	setUp
	test1
	tearDown
	setUp
	test2
	tearDown
oppure con test1 e test2 scambiati.

Dove mettere i test?

Le classi di test possono essere messe nella stessa directory delle classi da testare, in modo che abbiano accesso ai membri protected. Una variazione può essere quella di mettere la classe di test nello stesso package della classe da testare, ma in una diversa directory.

Riprendendo l'esempio della divisione, dichiarando DivTest come facente parte del package algebra, si possono strutturare le directory così:

	src/algebra/Div.java
	test/algebra/DivTest.java
In questo modo i test sono ben separati dai sorgenti, pur appartenendo allo stesso package! Naturalmente quando si eseguono i test le directory src/ e test/ - oltre a junit-4.0.jar - devono essere incluse nel classpath:
	java -cp <path programma>/src:<path programma>/test:<path junit>/junit-4.0.jar
		org.junit.runner.JUnitCore algebra.DivTest

Quando scrivere i test?

Molti ritengono che sia una buona pratica scrivere i test prima del codice, in modo che la scrittura del codice sia diretta verso la riuscita dei test. Questo implica che i test siano buoni, ovvero che verifichino anche situazioni particolari, come la divisione per 0 nel nostro esempio. Per questo l'impegno nel preparare i test dovrebbe essere pari a quello speso nello scrivere il codice da testare.



Indice
Prossimo capitolo - Organizzare lo sviluppo con Ant