W3docs

Introduzione al Testing in Java

Perché il testing è importante in Java, la piramide dei test e una panoramica dei principali framework di testing Java.

Il testing automatizzato è il modo in cui si dimostra che il codice fa quello che si pensa faccia—e si continua a dimostrarlo man mano che il codice cambia. Invece di eseguire il programma a mano e controllare l'output a occhio, si scrivono piccoli programmi che esercitano il proprio codice e verificano i risultati automaticamente. In Java questo ecosistema è costruito attorno a framework come JUnit e Mockito, ma le idee sottostanti—arrange, act, assert—sono abbastanza semplici da scrivere a mano. Questo capitolo traccia il panorama prima che i capitoli successivi approfondiscano ogni strumento.

Perché il testing automatizzato è importante

Un test è un controllo piccolo e ripetibile che verifica che un pezzo di codice si comporti correttamente. Il vantaggio non si ottiene alla prima esecuzione—lo si ottiene a ogni esecuzione successiva. Una volta che un comportamento è catturato in un test, qualsiasi modifica che lo rompe fallisce in modo evidente e immediato, invece di emergere come un bug in produzione settimane dopo. I test documentano anche l'intento: un test ben nominato dice cosa il codice dovrebbe fare.

// A test names a behavior, runs the code, and asserts the outcome.
@Test
void addsTwoPositiveNumbers() {
    int result = Calculator.add(2, 3);
    assertEquals(5, result);   // fails the build if result != 5
}

L'obiettivo è il feedback rapido. Una suite di test verde significa che si può fare refactoring con fiducia; una rossa indica esattamente cosa si è rotto.

La piramide dei test

I test si suddividono in livelli, solitamente rappresentati come una piramide. I test unitari si trovano alla base: molti, veloci, ognuno controlla una classe o un metodo in isolamento. I test di integrazione si trovano nel mezzo: meno numerosi, più lenti, verificano che i componenti funzionino insieme (il proprio codice insieme a un database, per esempio). I test end-to-end (E2E) si trovano in cima: pochi, i più lenti, guidano l'intera applicazione come farebbe un utente.

LivelloAmbitoVelocitàQuantitàStrumenti Java
Unituna classe/metodoveloce (ms)moltiJUnit, AssertJ
Integrationdiversi componentimediaalcuniJUnit, Testcontainers
End-to-endintero sistemalentopochiSelenium, REST-assured

La forma è importante: affidarsi ai test unitari economici e veloci per la maggior parte della copertura, e riservare i test E2E lenti e fragili a un numero limitato di percorsi utente critici.

Il pattern arrange–act–assert

Quasi ogni test, in qualsiasi framework, segue la stessa struttura in tre passi. Arrange: si preparano gli input e le eventuali dipendenze. Act: si invoca il codice in esame. Assert: si verifica che il risultato corrisponda a quanto atteso. Mantenere questi passi visivamente separati rende un test facile da leggere e da diagnosticare quando fallisce.

@Test
void rejectsBlankUsername() {
    // Arrange
    UserService service = new UserService();

    // Act
    boolean valid = service.isValidUsername("   ");

    // Assert
    assertFalse(valid);
}

Un'asserzione che fallisce lancia un'eccezione, il framework la registra e l'esecuzione prosegue al test successivo—così un comportamento difettoso non nasconde mai gli altri.

JUnit, il runner standard

JUnit è il framework di unit-testing de facto per Java. Si annotano i metodi con @Test, JUnit li scopre tramite reflection, esegue ognuno e riporta pass/fail. Le asserzioni come assertEquals, assertTrue e assertThrows sono helper statici che fanno fallire il test quando l'aspettativa non è soddisfatta. I progetti reali eseguono JUnit tramite un build tool (il plugin Surefire di Maven o il task test di Gradle), non a mano.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    @Test
    void dividesNumbers() {
        assertEquals(4, Calculator.divide(8, 2));
    }

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

Poiché in questo runner non è disponibile alcun JAR di JUnit né un build tool, l'esempio seguente ricostruisce la stessa idea da zero—un piccolo harness che esegue controlli nominati e conta i pass e i fail, esattamente quello che @Test più assertEquals fanno internamente.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Ogni chiamata a assertEquals è un caso di test—si preparano gli input, si agisce chiamando add o isBlank, e si verifica il risultato—rispecchiando esattamente quello che fa un metodo JUnit @Test.
  • Un controllo superato stampa PASS e uno fallito stampa FAIL con sia il valore atteso che quello effettivo, che è la diagnostica fornita dai messaggi di asserzione di JUnit.
  • Il caso volutamente sbagliato (expected 10 but got 5) mostra come appare un test rosso: l'harness continua a eseguire i controlli rimanenti invece di fermarsi al primo fallimento.
  • Il riepilogo conta 5 totali, 4 passati, 1 fallito—lo stesso report pass/fail che un test runner stampa alla fine di un'esecuzione.
  • Poiché un test è fallito, il programma termina con BUILD FAILURE, dimostrando perché un singolo test difettoso dovrebbe far fallire l'intera build in CI.

Come si incastrano i pezzi

Gli strumenti di testing Java si sovrappongono l'uno all'altro, dalle asserzioni grezze fino alla piena integrazione con il build:

  1. Le asserzioni (assertEquals, assertThrows) stabiliscono cosa deve essere vero.
  2. JUnit scopre ed esegue i metodi @Test e riporta i risultati.
  3. Mockito fornisce collaboratori fittizi in modo che un'unità possa essere testata in isolamento.
  4. Maven o Gradle integra la suite nel build, facendola fallire in presenza di qualsiasi test rosso.
  5. CI esegue il build a ogni push, in modo che il codice difettoso non raggiunga mai il branch principale.

Ogni capitolo successivo affronta un gradino di questa scala—prima le annotazioni e le asserzioni di JUnit, poi il mocking con Mockito, poi l'integrazione dei test in Maven e Gradle. Capire dove si colloca ogni strumento mantiene coerente l'intera storia del testing.

Pratica

Pratica
Nel pattern arrange-act-assert, cosa fa il passo 'assert'?
Nel pattern arrange-act-assert, cosa fa il passo 'assert'?
Was this page helpful?