Sei sulla pagina 1di 6

Confronto tra Junit 3 e Junit 4

Metodi di test

Tutte le versioni precedenti di JUnit utilizzano convenzioni di codifica e riflessione per


individuare i test. Per esempio, il codice seguente test verifica che 1 + 1 sia uguale a
2:
1. import junit.framework.TestCase;
2.
3. public class AdditionTest extends TestCase {
4.
5. private int x = 1;
6. private int y = 1;
7.
8. public void testAddition() {
9. int z = x + y;
10. assertEquals(2, z);
11. }
12. }
Al contrario in JUnit 4 i test sono indicati da una annotazione @Test, come illustrato di
seguito:
1.import org.junit.Test;
2.import junit.framework.TestCase;
3.
4.public class AdditionTest extends TestCase {
5.
6. private int x = 1;
7. private int y = 1;
8.
9. @Test public void testAddition() {
10. int z = x + y;
11. assertEquals(2, z);
12. }
13.}
In questo modo non è più necessario chiamare i metodi testQualcosa1(),
testQualcosa2(), e così via. Per esempio, anche il seguente approccio funziona
egregiamente:
1.import org.junit.Test;
2.import junit.framework.TestCase;
3.
4.public class AdditionTest extends TestCase {
5.
6. private int x = 1;
7. private int y = 1;
8.
9. @Test public void additionTest() {
10. int z = x + y;
11. assertEquals(2, z);
12. }
13.}
Come anche questo…
1. import org.junit.Test;
2. import junit.framework.TestCase;
3.
4. public class AdditionTest extends TestCase {
5.
6. private int x = 1;
7. private int y = 1;
8.
9. @Test public void addition() {
10. int z = x + y;
11. assertEquals(2, z);
12. }
13.}
Questo permette di seguire la convenzione di codifica che meglio si adatta alle proprie
applicazioni e al proprio stile di programmazione. Per esempio, alcuni programmatori
adottano una convenzione in cui la classe di test usa gli stessi nomi dei metodi della
classe da testare: List.contains() viene testato da ListTest.contains(), List.addAll() viene
testato da ListTest.addAll(), e così via.
La classe TestCase è ancora presente, ma non è più necessario estenderla. Finché si
annotano i metodi di test con @Test, si possono inserire metodi di test in qualsiasi
classe. È però necessario importare la classe junit.Assert per accedere ai vari metodi
assert, come nel codice seguente:
1. import org.junit.Assert;
2.
3. public class AdditionTest {
4.
5. private int x = 1;
6. private int y = 1;
7.
8. @Test public void addition() {
9. int z = x + y;
10. Assert.assertEquals(2, z);
11. }
12.}
È anche possibile usare la nuova funzionalità di importazione static del JDK 5 per
rendere tutto ciò più semplice rispetto alla vecchia versione di JUnit:
1. import static org.junit.Assert.assertEquals;
2.
3. public class AdditionTest {
4.
5. private int x = 1;
6. private int y = 1;
7.
8. @Test public void addition() {
9. int z = x + y;
10. assertEquals(2, z);
11. }
12.}
Questo approccio rende il testing di metodi protetti assai più facile, perché la classe di
test può ora estendere la classe che contiene i metodi protetti.

I metodi SetUp e TearDown

Gli strumenti di esecuzione dei test di JUnit 3 eseguono automaticamente il metodo


setUp() prima di eseguire ciascun metodo di test. Di norma, si usa setUp() per
inizializzare i dati necessari per l’esecuzione dei test, reimpostare eventuali variabili
d’ambiente, e così via. Per esempio, ecco un metodo setUp():
1. protected void setUp() {
2.
3. System.setErr(new PrintStream(new ByteArrayOutputStream()));
4.
5. inputDir = new File("data");
6. inputDir = new File(inputDir, "xslt");
7. inputDir = new File(inputDir, "input");
8.
9. }
Anche in JUnit 4 è possibile inizializzare i dati e configurare l’ambiente prima
dell’esecuzione di ogni metodo di test: però il metodo che esegue tali operazioni non
deve necessariamente chiamarsi setUp(). Deve solo essere marcato con l’annotazione
@Before:
1. @Before protected void initialize() {
2.
3. System.setErr(new PrintStream(new ByteArrayOutputStream()));
4.
5. inputDir = new File("data");
6. inputDir = new File(inputDir, "xslt");
7. inputDir = new File(inputDir, "input");
8. }
È altresì possibile avere diversi metodi annotati con @Before: in tal caso, ciascuno di
essi sarà eseguito prima di ogni test:
1. @Before protected void findTestDataDirectory() {
2. inputDir = new File("data");
3. inputDir = new File(inputDir, "xslt");
4. inputDir = new File(inputDir, "input");
5. }
6. @Before protected void redirectStderr() {
7. System.setErr(new PrintStream(new ByteArrayOutputStream()));
8. }
Anche le operazioni di cleanup operano in modo analogo. In JUnit 3, si usa il metodo
tearDown(): si noti, in questo frammento di codice, la chiamata alla Garbage Collection
per forzare il recupero di memoria.
1. protected void tearDown() {
2. doc = null;
3. System.gc();
4. }
Con JUnit 4, è sufficiente annotare un metodo qualsiasi con @After:
1. @After protected void disposeDocument() {
2. doc = null;
3. System.gc();
4. }
Come con l’annotazione @Before, si possono creare diversi metodi di cleanup annotati
con @After, ciascuno dei quali sarà eseguito dopo ogni test.
Inoltre, non è più necessario chiamare esplicitamente i metodi di inizializzazione e di
cleanup della superclasse. Fino a quando non vengono sovrascritti, il “test runner”
chiamerà questi metodi automaticamente, se necessario. I metodi @Before presenti
nelle superclassi sono invocati prima prima dei metodi @Before delle sottoclassi (si
noti come questo comportamento rispecchi l’ordine di chiamata dei costruttori). I
metodi @After sono eseguiti in senso inverso: prima quelli delle sottoclassi e poi quelli
delle superclassi.

Inizializzazione delle classi di test

JUnit 4 introduce anche una nuova funzionalità che non ha equivalenti in JUnit 3: un
metodo di inizializzazione, simile a setUp(), che opera a livello di classe. Ogni metodo
annotato @BeforeClass sarà eseguito una volta, subito dopo il caricamento della
classe di test, e prima che i metodi di test siano eseguiti; di contro, ogni metodo
annotato con @AfterClass verrà eseguito una sola volta, dopo che tutti i metodi di test
sono stati eseguiti.
Per esempio, si supponga che ciascuna classe di test usi una connessione a un
database, una connessione di rete, una grande struttura dati, o qualche altra risorsa
che è particolarmente oneroso inizializzare o scaricare. Invece di ricreare queste
risorse prima di ogni test, è possibile inizializzarle e deinizializzarle una sola volta.
Questo approccio renderà l’esecuzione di alcuni casi di test assai più rapida.
1. private PrintStream systemErr;
2.
3. @BeforeClass protected void redirectStderr() {
4. systemErr = System.err; // Hold on to the original value
5. System.setErr(new PrintStream(new ByteArrayOutputStream()));
6. }
7.
8. @AfterClass protected void tearDown() {
9. // restore the original value
10. System.setErr(systemErr);
11.}

Come testare le eccezioni

Il meccanismo di test delle eccezioni è uno dei principali miglioramenti apportato a


JUnit 4. Nelle vecchie versioni di Junit si usava un blocco try contenente il codice che
dovrebbe generare l’eccezione, e una chiamata a fail() dalla fine del blocco try. Per
esempio, il codice seguente verifica che sia generata una ArithmeticException:
1. public void testDivisionByZero() {
2.
3. try {
4. int n = 2 / 0;
5. fail("Divisione per zero.");
6. }
7. catch (ArithmeticException success) {
8. assertNotNull(success.getMessage());
9. }
10.}
In JUnit 4, è possibile scrivere esplicitamente il codice che genera l’eccezione:
un’annotazione dichiara che è previsto che sia sollevata un’eccezione:
1. @Test(expected=ArithmeticException.class)
2. public void divideByZero() {
3. int n = 2 / 0;
4. }
Se l’eccezione non viene sollevata o se è diversa da quella prevista, il test avrà esito
negativo. Si tenga presente che potrebbe ancora essere utile il blocco try-catch
vecchio stile, per esempio se si desidera verificare il messaggio d’errore, eventuali
dettagli dell’eccezione o altre proprietà.

Impostazione di tempi limite

La verifica delle prestazioni è una delle aree più spinose nel mondo dei test di unità.
JUnit 4 non risolve completamente il problema, ma offre un utile supporto: infatti, i
metodi di test possono essere annotati con il parametro timeout. Se il test viene
eseguito in un tempo superiore a quello indicato, fallisce. Per esempio, nel codice
seguente il test non riesce se il metodo richiede più di 500 millisecondi per essere
eseguito:
1. @Test(timeout=500) public void retrieveAllElementsInDocument() {
2. doc.query("//*");
3. }
Oltre al semplice benchmarking, i test cronometrati sono utili anche per i test su
operazioni di rete. Se un host remoto o un database di test a cui si sta tentando di
connettersi sono lenti o irraggiungibili, è possibile bypassare il test in modo da non
rallentare i test seguenti:
1. @Test(timeout=2000)
2. public void remoteBaseRelativeResolutionWithDirectory()
3. throws IOException, ParsingException {
4. builder.build("http://www.qualcosa.org/xml");
5. }

Test ignorati

L’annotazione @Ignore consente di marcare i metodi di test che per qualche ragione si
desidera saltare. Si supponga di avere un metodo di test che richiede un tempo molto
elevato per la sua esecuzione: non necessariamente si deve tentare di renderlo più
veloce; può essere che il suo lavoro sia semplicemente assai complessa o
naturalmente lento. I test che tentano di accedere a server remoti si trovano spesso in
questa categoria. Per non rallentare gli altri metodi, è possibile annotare (anche
temporaneamente) questo test in modo che sia ignorato.
1. @Ignore public void testUTF32BE()
2. throws ParsingException, IOException, XIncludeException {
3.
4. File input = new File(
5. "data/xinclude/input/UTF32BE.xml"
6. );
7. Document doc = builder.build(input);
8. Document result = XIncluder.resolve(doc);
9. Document expectedResult = builder.build(
10. new File(outputDir, "UTF32BE.xml")
11. );
12. assertEquals(expectedResult, result);
13.}
Durante l’esecuzione della classe di test, questo metodo sarà ignorato.

Nuove asserzioni

JUnit 4 aggiunge due nuovi metodi assert() per il confronto di array; di seguito le
definizioni:
1. public static void assertArrayEquals(Object[] expecteds, Object[] actuals)
2. public static void assertArrayEquals(String message, Object[] expecteds,
3. Object[] actuals)
Questi due metodi confrontano gli array nel modo più ovvio: due array sono uguali se
sono della stessa dimensione e se ciascun elemento è uguale a quello corrispondente
nell’altro array.