Annotazioni JUnit in Java
Annotazioni principali di JUnit 5 — @Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll, @Disabled.
JUnit 5 è il framework di test standard de facto per Java, e quasi tutto ciò che gli si comunica avviene tramite annotazioni. Non si scrive un metodo main né si chiamano i metodi manualmente; si decorano i metodi ordinari con annotazioni come @Test, @BeforeEach e @AfterAll, e il motore JUnit li individua tramite riflessione e li esegue nell'ordine corretto. Questo capitolo tratta le annotazioni principali del ciclo di vita — cosa significa ciascuna, quando viene attivata e come si combinano per garantire a ogni test una fixture pulita e isolata.
Se sei nuovo al framework, inizia con l'introduzione a JUnit; per gli helper di asserzione che questi test utilizzano, consulta le asserzioni JUnit. Le annotazioni stesse sono una funzionalità generale di Java trattata in le annotazioni Java.
Le annotazioni si trovano in org.junit.jupiter.api
L'API di JUnit 5 è il modulo Jupiter. Le annotazioni che si usano quotidianamente provengono tutte da un unico package:
| Annotazione | Si applica a | Viene eseguita |
|---|---|---|
@Test | un metodo | una volta per ogni metodo di test |
@BeforeEach | un metodo | prima di ogni @Test |
@AfterEach | un metodo | dopo ogni @Test |
@BeforeAll | un metodo static | una volta, prima di qualsiasi test nella classe |
@AfterAll | un metodo static | una volta, dopo tutti i test nella classe |
@Disabled | un metodo o una classe | mai (viene saltato e segnalato) |
@DisplayName | un metodo o una classe | imposta un nome leggibile nei report |
Un metodo contrassegnato con @Test non necessita del modificatore public in JUnit 5 (package-private va bene) e deve restituire void.
@Test: l'unità di lavoro
Un metodo di test verifica qualcosa tramite gli helper statici in org.junit.jupiter.api.Assertions. Se un'asserzione fallisce viene lanciata un'eccezione, e il motore registra quel singolo test come fallito senza interrompere gli altri.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void addsTwoNumbers() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
@Test
void throwsOnDivideByZero() {
Calculator calc = new Calculator();
assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
}
}Ogni @Test viene eseguito su una nuova istanza della classe di test — JUnit costruisce un nuovo oggetto per ogni test per impostazione predefinita, quindi i campi impostati in un test non possono influenzare un altro.
@BeforeEach e @AfterEach: fixture per test
La configurazione di cui ogni test ha bisogno va in un metodo @BeforeEach; il teardown va in @AfterEach. Questi metodi racchiudono ogni @Test, fornendo a ogni test un punto di partenza identico.
import org.junit.jupiter.api.*;
class OrderServiceTest {
private OrderService service;
@BeforeEach
void setUp() {
service = new OrderService(new InMemoryRepo()); // fresh state per test
}
@AfterEach
void tearDown() {
service.close(); // runs even if the test threw
}
@Test
void placesOrder() {
assertTrue(service.place("SKU-1", 2));
}
}@AfterEach viene eseguito anche quando il test fallisce, il che lo rende il posto giusto per rilasciare le risorse aperte in @BeforeEach.
@BeforeAll e @AfterAll: una volta per classe
Quando la configurazione è costosa e condivisibile — un container di database, un server embedded avviato — si usa @BeforeAll per eseguirla una volta e @AfterAll per smantellarlo una volta. Poiché vengono eseguiti prima che esista qualsiasi istanza, devono essere static.
import org.junit.jupiter.api.*;
class RepositoryTest {
static Database db;
@BeforeAll
static void startDatabase() {
db = Database.start(); // runs once, before everything
}
@AfterAll
static void stopDatabase() {
db.stop(); // runs once, after everything
}
@Test
void savesRow() {
assertEquals(1, db.insert("hello"));
}
}L'ordine completo del ciclo di vita per una classe con due test è: @BeforeAll → (@BeforeEach → @Test → @AfterEach) → (@BeforeEach → @Test → @AfterEach) → @AfterAll. Il capitolo sul ciclo di vita JUnit approfondisce questo ordinamento, incluso come interagisce con la creazione delle istanze.
@Disabled: saltare senza eliminare
@Disabled disattiva un test (o un'intera classe). Il motore lo segnala come saltato anziché passato o fallito, in modo che rimanga visibile. Fornire sempre un motivo.
@Test
@Disabled("flaky until the rate-limiter fix lands — see JIRA-1234")
void callsExternalApi() {
// not executed
}Un esempio pratico: un mini motore di test
Non c'è nessun jar JUnit in questo runner, quindi il programma seguente costruisce un piccolo motore proprio con la stessa struttura di JUnit. Dichiara annotazioni marcatori (@BeforeAll, @BeforeEach, @Test, @AfterEach, @AfterAll, @Disabled), definisce una piccola classe di test annotata, quindi usa la riflessione — esattamente ciò che fa internamente il motore di JUnit — per individuare ed eseguire i metodi nell'ordine del ciclo di vita e stampare un riepilogo pass/fail/skip.
Cosa osservare dall'esecuzione:
@BeforeAllviene stampato esattamente una volta all'inizio e@AfterAllesattamente una volta alla fine — la configurazione e il teardown a livello di classe racchiudono l'intera esecuzione, motivo per cui JUnit richiede che sianostatic.- Ogni
@Testeseguito è preceduto da una riga@BeforeEache seguito da una riga@AfterEach, quindi ogni test è stato eseguito su una fixture preparata di fresco e ha fatto pulizia dopo di sé — il bracketing per-test che mantiene i test indipendenti. - Il metodo
flakyportava@Disabled, quindi ha stampato(skipped via @Disabled)e il suo corpo non è mai stato eseguito; l'asserzione fallente al suo interno non è mai stata raggiunta, che è proprio il punto di disabilitare anziché eliminare. - Il ciclo di scoperta agisce solo sui metodi dove
isAnnotationPresent(Test.class)è vero — le annotazioni sono solo metadati, ed è il motore che le legge tramite riflessione a trasformarle in comportamento, esattamente come funziona il vero JUnit. - La riga finale riporta
2 passed, 0 failed, 1 skipped: due test reali sono passati, nessuno è fallito, e quello disabilitato è stato conteggiato come saltato anziché ignorato silenziosamente — la stessa contabilità pass/fail/skip che fornisce un report JUnit.