W3docs

Test parametrizzati JUnit in Java

Esegui lo stesso test JUnit con input diversi usando @ParameterizedTest e sorgenti di valori.

Un test parametrizzato esegue lo stesso metodo di test più volte, una volta per ogni insieme di input fornito. Invece di copiare e incollare testReverseAbc, testReverseEmpty e testReverseSingle, scrivi la logica una volta sola e fornisci una sorgente di dati — un elenco di input e risultati attesi. JUnit 5 (il motore Jupiter) rende tutto ciò di prima classe con @ParameterizedTest e una famiglia di annotazioni sorgente. Il vantaggio è un minor numero di righe, una copertura più densa e ogni input riportato con il proprio esito pass/fail.

Questo capitolo presuppone che tu sappia già come si scrive e si asserisce un test semplice; in caso contrario, inizia con l'introduzione a JUnit e le asserzioni JUnit. Tratta quando ricorrere a un test parametrizzato, come scegliere una sorgente di argomenti (@ValueSource, @CsvSource, @MethodSource e altre) e l'errore più comune — un valore atteso sbagliato anziché un bug nel codice.

Da test ripetuti a un unico test parametrizzato

Un metodo @Test semplice testa esattamente uno scenario. Quando vuoi verificare lo stesso comportamento su una tabella di input, l'approccio ingenuo ripete il metodo:

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

class PrimesTest {
  @Test void two_isPrime()   { assertTrue(Primes.isPrime(2)); }
  @Test void seven_isPrime() { assertTrue(Primes.isPrime(7)); }
  @Test void thirteen_isPrime() { assertTrue(Primes.isPrime(13)); }
}

La versione parametrizzata riduce tutti e tre in un unico metodo. Si annota con @ParameterizedTest (non @Test) e si collega una sorgente che fornisce l'argomento per ogni esecuzione:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PrimesTest {
  @ParameterizedTest
  @ValueSource(ints = {2, 7, 13})
  void isPrime(int candidate) {
    assertTrue(Primes.isPrime(candidate));
  }
}

JUnit invoca isPrime tre volte — candidate=2, poi 7, poi 13 — e riporta tre risultati. Un valore fallito non nasconde gli altri.

Scegliere una sorgente di argomenti

L'annotazione @ParameterizedTest è inutile da sola; ha bisogno di una sorgente che produca gli argomenti. JUnit Jupiter ne include diverse, ognuna adatta a una diversa forma di dati:

SorgenteFornisceIdeale per
@ValueSourceUn singolo letterale per esecuzione (ints, strings, doubles, …)Test con un argomento
@CsvSourceUna riga di valori separati da virgola per esecuzionePoche righe inline con più colonne
@CsvFileSourceRighe lette da un file .csv nel classpathTabelle grandi o gestite esternamente
@MethodSourceCiò che un metodo factory restituisce come Stream/CollectionOggetti complessi, casi calcolati
@EnumSourceLe costanti di un enumCopertura esaustiva di un enum
@NullSource / @EmptySourceValori null e vuotiCopertura dei casi limite per stringhe/collezioni

La regola pratica: @ValueSource per un singolo input semplice, @CsvSource per una piccola tabella multicolonna, e @MethodSource quando i dati non entrano più nei letterali dell'annotazione.

Più colonne con @CsvSource

Quando ogni caso ha sia un input che un output atteso, @CsvSource ti offre una piccola tabella inline. Ogni stringa è una riga; le virgole la suddividono nei parametri del metodo in ordine:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

class StringsTest {
  @ParameterizedTest
  @CsvSource({
      "abc,     cba",
      "racecar, racecar",
      "'',      ''"          // single quotes denote an empty string
  })
  void reverse(String input, String expected) {
    assertEquals(expected, Strings.reverse(input));
  }
}

JUnit converte ogni token separato da virgola nel tipo di parametro dichiarato, quindi @CsvSource({"4, 16"}) può essere passato a (int n, int square). Usa le virgolette singole per includere virgole o stringhe vuote all'interno di una cella.

Casi calcolati con @MethodSource

I valori delle annotazioni devono essere costanti a tempo di compilazione, quindi una volta che gli argomenti sono oggetti reali o richiedono calcoli, passa a @MethodSource. Questo nomina un metodo statico che restituisce uno Stream<Arguments> (o qualsiasi Collection/array):

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;

class TaxTest {
  static Stream<Arguments> brackets() {
    return Stream.of(
        Arguments.of(0,      0.0),
        Arguments.of(10_000, 1_000.0),
        Arguments.of(50_000, 7_500.0)
    );
  }

  @ParameterizedTest(name = "income {0} -> tax {1}")
  @MethodSource("brackets")
  void computesTax(int income, double expectedTax) {
    assertEquals(expectedTax, Tax.of(income));
  }
}

L'attributo name opzionale personalizza come ogni invocazione appare nel report di test, con {0}, {1} al posto degli argomenti — prezioso quando una singola riga fallita deve essere identificata a colpo d'occhio.

Un esempio pratico: un runner parametrizzato senza JUnit

Il code runner non ha JUnit nel suo classpath, quindi questo programma modella il meccanismo che un test parametrizzato incarna con puro codice JDK: un singolo controllo è definito una volta, poi eseguito su un elenco di casi — esattamente ciò che fa @ParameterizedTest dietro le annotazioni. Un caso è deliberatamente sbagliato così puoi vedere come le singole righe passano o falliscono in isolamento.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il blocco reverse stampa quattro righe PASS e >> reverse: 4 passed, 0 failed — un corpo (reverse) è stato eseguito contro quattro righe, rispecchiando come un singolo metodo @ParameterizedTest viene invocato una volta per ogni riga di @CsvSource.
  • Il blocco isPrime stampa PASS per gli input 2, 7, 9 e 1, ma FAIL per l'input 4, perché isPrime(4) restituisce false mentre la riga affermava true — un'aspettativa sbagliata, non un bug nel codice, che è l'errore più comune nei test parametrizzati.
  • Quel singolo fallimento è riportato sulla propria riga e conteggiato come >> isPrime: 4 passed, 1 failed; le altre righe passano comunque, dimostrando il vantaggio chiave rispetto a un ciclo scritto a mano con un'unica asserzione — ogni input è un caso indipendente, riportato individualmente.
  • L'helper runAll prende l'unità come Function e i casi come List, separando la logica sotto test dai dati — esattamente la separazione che @ParameterizedTest più una sorgente di argomenti offre.
  • Ogni riga mostra expected accanto ad actual, quindi la riga 4 / expected=true / actual=false indica esattamente quale valore è discordante — lo stesso valore diagnostico fornito dal messaggio di assertEquals di JUnit e dal template name = "...".

Quando usare un test parametrizzato

Ricorri a @ParameterizedTest quando un comportamento deve valere per una tabella di input — valori limite, classi di equivalenza o un elenco di regressione di input che in passato hanno causato problemi. Continua a usare un semplice @Test quando uno scenario richiede una configurazione unica o asserzioni distinte; ammassare casi non correlati in un unico metodo parametrizzato rende solo il report più difficile da leggere. Per la configurazione condivisa tra entrambi gli stili, vedi il capitolo sul ciclo di vita dei test, e per il vocabolario completo delle asserzioni usate all'interno di ogni esecuzione, il capitolo sulle asserzioni.

Esercitazione

Pratica
In JUnit 5, il tuo test deve eseguire una volta per ogni riga di una tabella in cui ogni riga contiene sia una stringa di input che la stringa invertita attesa. Quale singola annotazione è più appropriata per fornire quei dati inline?
In JUnit 5, il tuo test deve eseguire una volta per ogni riga di una tabella in cui ogni riga contiene sia una stringa di input che la stringa invertita attesa. Quale singola annotazione è più appropriata per fornire quei dati inline?
Was this page helpful?