W3docs

Introduzione a JUnit in Java

Cos'è JUnit, come aggiungerlo a un progetto Java e come scrivere il primo test JUnit.

JUnit è il framework standard de facto per la scrittura di test automatizzati in Java. Un test è semplicemente un piccolo metodo che esegue una parte del codice e asserisce che si sia comportato come previsto; il compito di JUnit è scoprire quei metodi, eseguire ciascuno in isolamento, controllare le asserzioni e segnalare quali sono passati e quali hanno fallito. La generazione attuale, JUnit 5 (chiamata anche JUnit Jupiter), viene distribuita come un insieme di piccole librerie da aggiungere alla build — non fa parte del JDK — e alimenta il passaggio mvn test / gradle test che funge da gate per quasi ogni CI di un progetto Java.

Perché usare un framework di testing

Potresti verificare il codice manualmente con metodi main e println — ma questo approccio non scala. Un framework ti fornisce quattro cose che altrimenti dovresti ricostruire da zero:

  • Discovery — trova automaticamente ogni metodo @Test; non devi mai mantenere una lista.
  • Isolation — ogni test riceve una fixture aggiornata, quindi un test non può corrompere un altro.
  • Assertions — un vocabolario ricco (assertEquals, assertThrows, …) che produce messaggi di errore precisi.
  • Reporting — un riepilogo uniforme pass/fail che lo strumento di build e l'IDE comprendono.

Implementare queste cose correttamente una volta rende i test economici da scrivere, che è l'obiettivo principale: i test economici vengono scritti, e il codice testato può essere modificato senza timore.

Aggiungere JUnit a un progetto

JUnit 5 è una dipendenza, dichiarata nel file di build. Con Maven, l'aggregatore junit-jupiter include l'API (per la compilazione) e il motore (per l'esecuzione):

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.11.3</version>
  <scope>test</scope>
</dependency>

Con Gradle sono due righe più lo switch useJUnitPlatform():

dependencies {
  testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
}
test {
  useJUnitPlatform()
}

I sorgenti di test risiedono in src/test/java, specchiando il package della classe che esercitano. Lo scope test mantiene JUnit fuori dall'artefatto di produzione.

Il primo test

Un test JUnit è un metodo ordinario annotato con @Test. Al suo interno si chiama il codice sotto test e si asserisce sul risultato. Ecco una classe Calculator e una classe di test per essa:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTest {

  private final Calculator calc = new Calculator();

  @Test
  void addReturnsSum() {
    assertEquals(5, calc.add(2, 3));
  }

  @Test
  void divideByZeroThrows() {
    assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
  }
}

Nota il pattern che ogni test segue — Arrange (preparare una fixture), Act (chiamare il metodo), Assert (verificare il risultato). I metodi di test sono package-private (non serve public in JUnit 5) e restituiscono void. Esegui mvn test e la build diventa verde solo se ogni asserzione tiene.

Le annotazioni e le asserzioni più usate

La superficie di JUnit è ridotta. Questi pochi membri coprono la grande maggioranza dei test reali:

MembroPackage / classeScopo
@Testorg.junit.jupiter.apiContrassegna un metodo come test
@BeforeEach / @AfterEachorg.junit.jupiter.apiEseguiti prima/dopo ogni test (setup / teardown delle fixture)
@BeforeAll / @AfterAllorg.junit.jupiter.apiEseguiti una volta prima/dopo tutti i test della classe
@DisplayNameorg.junit.jupiter.apiUn nome leggibile dall'uomo per i report
@Disabledorg.junit.jupiter.apiSalta temporaneamente un test
assertEquals(exp, act)AssertionsFallisce se i due valori non sono uguali
assertTrue / assertFalseAssertionsFallisce se una condizione booleana non vale
assertThrows(type, exec)AssertionsFallisce se la lambda non lancia quell'eccezione
assertNull / assertNotNullAssertionsFallisce se la nullità non corrisponde

@BeforeEach è ciò che dà ad ogni test una base pulita — JUnit crea una nuova istanza della classe di test per ogni @Test, poi esegue il setup, quindi lo stato non fuoriesce mai tra un test e l'altro.

Cosa fa JUnit per te, in un file eseguibile

Il code runner qui non ha JUnit nel suo classpath (è una libreria esterna, non parte del JDK), quindi l'esempio seguente reimplementa il ciclo principale di JUnit in Java puro: una fixture ricreata prima di ogni test, un piccolo insieme di helper assertXxx, un elenco di metodi di test eseguiti indipendentemente e un conteggio pass/fail alla fine. Questa è esattamente la macchina che JUnit automatizza — vederla nuda rende il framework reale ovvio. Un test fallisce deliberatamente così puoi vedere come appare il rosso.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • I tre test corretti stampano PASS e il quarto stampa FAIL deliberatelyFailing -> expected <10> but was <5> — un messaggio di errore preciso, non solo "un test ha fallito." Quella differenza (expected … but was …) è esattamente ciò che assertEquals di JUnit fornisce, ed è ciò che rende un test rosso diagnosticabile a colpo d'occhio.
  • setUp() viene eseguito prima di ogni test, quindi calc è una Calculator nuova ogni volta. Questo è il contratto di @BeforeEach: i test sono isolati, e l'ordine in cui vengono eseguiti non può mai importare perché nessuno di essi condivide stato mutabile.
  • divideThrowsOnZero passa asserendo che un'eccezione viene lanciata — assertThrows rende "questo dovrebbe fallire" una prima asserzione positiva invece di un fragile try/catch. Le eccezioni attese sono comportamenti degni di essere testati, non errori da ingoiare.
  • Il conteggio finale — Tests run: 4, Passed: 3, Failed: 1, Assertions: 5 — è il report. Un test fallito su quattro porta comunque l'intera build a RED; la CI tratta qualsiasi fallimento come uno stop, ecco perché una suite verde è significativa.
  • Nulla qui ha importato JUnit, eppure la forma è identica: discovery dei metodi annotati, setup per-test, asserzioni, riepilogo. Il valore di JUnit è che automatizza questo ciclo (e aggiunge discovery, parallelismo, test parametrizzati e integrazione con l'IDE) così scrivi solo i corpi dei test.

Cosa tratta il resto di questa parte

Questa parte si basa sul ciclo principale che hai appena visto:

  • Annotazioni JUnit@Test, @DisplayName, @Disabled e il resto del set di marker.
  • Il ciclo di vita del test — come @BeforeEach / @AfterEach / @BeforeAll / @AfterAll forniscono ad ogni test una fixture pulita.
  • Asserzioni — il catalogo completo di assertXxx, incluso assertThrows per i test delle eccezioni.
  • Test parametrizzati — eseguire un corpo di test su molti input invece di copiare e incollare i casi.
  • Mocking con Mockito — sostituire i collaboratori di una classe con sostituti affinché un unit test rimanga un test unitario.

Se vuoi il quadro generale del perché i test automatizzati sono importanti prima di immergerti nell'API, consulta Testing in Java. Altrimenti il prossimo capitolo inizia dove ogni suite comincia: definire una classe di test ed eseguirla.

Esercitazione

Pratica
In JUnit 5, cosa garantisce l'annotazione di un metodo con @BeforeEach riguardo alla fixture di test?
In JUnit 5, cosa garantisce l'annotazione di un metodo con @BeforeEach riguardo alla fixture di test?
Was this page helpful?