W3docs

Ciclo di vita dei test JUnit in Java

Ciclo di vita delle istanze e comportamento per metodo vs. per classe in JUnit 5.

Ogni test JUnit 5 viene eseguito all'interno di un ciclo di vita ben definito: una sequenza di hook di setup e teardown che si attivano intorno ai metodi @Test in un ordine garantito. Comprendere quell'ordine — e la regola secondo cui JUnit crea una nuova istanza della classe di test per ogni metodo di test — è ciò che distingue suite di test fragili e dipendenti dall'ordine da quelle pulite e isolate. Questo capitolo illustra le cinque annotazioni del ciclo di vita e i due cicli di vita delle istanze offerti da JUnit.

Le cinque annotazioni del ciclo di vita

JUnit 5 (il package org.junit.jupiter.api) definisce quattro annotazioni di callback che racchiudono i test, più @Test stesso. Per una panoramica più completa di ciascuna, vedere annotazioni JUnit; se sei nuovo al framework, inizia con l'introduzione a JUnit.

AnnotazioneViene eseguitaIl metodo deve essere
@BeforeAllUna volta, prima di qualsiasi test nella classestatic (nel ciclo di vita predefinito)
@BeforeEachPrima di ogni metodo @Testistanza
@TestIl test stessoistanza
@AfterEachDopo ogni metodo @Testistanza
@AfterAllUna volta, dopo che tutti i test sono stati eseguitistatic (nel ciclo di vita predefinito)

Una singola classe di test con tre test attiva quindi @BeforeAll una volta, poi @BeforeEach@Test@AfterEach tre volte, quindi @AfterAll una volta.

import org.junit.jupiter.api.*;

class CalculatorTest {
  @BeforeAll  static void initSuite() { System.out.println("once, up front"); }
  @BeforeEach void setUp()           { System.out.println("before each test"); }

  @Test void add()      { Assertions.assertEquals(4, 2 + 2); }
  @Test void subtract() { Assertions.assertEquals(0, 2 - 2); }

  @AfterEach void tearDown()    { System.out.println("after each test"); }
  @AfterAll  static void close() { System.out.println("once, at the end"); }
}

Una nuova istanza per ogni metodo di test

La regola più importante del ciclo di vita: per impostazione predefinita JUnit costruisce una nuova istanza della classe di test prima di ogni metodo di test. I campi che si modificano in un test non possono influenzare un altro, perché il test successivo viene eseguito su un oggetto diverso. Questo è ciò che rende i test indipendenti dall'ordine di esecuzione.

class IsolationTest {
  private int counter = 0; // re-initialised for every test

  @Test void first()  { counter++; Assertions.assertEquals(1, counter); }
  @Test void second() { counter++; Assertions.assertEquals(1, counter); } // also 1, not 2
}

Entrambi i test vedono counter == 1. Se JUnit riutilizzasse un'unica istanza, il secondo test osserverebbe 2 e passerebbe o fallirebbe in base all'ordine — esattamente la fragilità che questo design previene.

PER_METHOD vs. PER_CLASS

È possibile rinunciare all'istanza per metodo con @TestInstance(Lifecycle.PER_CLASS). In tal caso JUnit crea una sola istanza per l'intera classe, i campi di istanza persistono tra i test e — per comodità — @BeforeAll/@AfterAll possono essere non-static.

AspettoPER_METHOD (predefinito)PER_CLASS
Istanze createuna per @Testuna per classe
Stato dei campi di istanzaresettato ad ogni testcondiviso tra i test
@BeforeAll/@AfterAlldevono essere staticpossono essere metodi di istanza
Adatto permassimo isolamentosetup condiviso costoso
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.TestInstance.Lifecycle;

@TestInstance(Lifecycle.PER_CLASS)
class SharedFixtureTest {
  @BeforeAll void openConnection() { /* non-static is now legal */ }
  @AfterAll  void closeConnection() { }
}

Ricorrere a PER_CLASS solo quando il setup è genuinamente costoso e sicuro da condividere. Il valore predefinito fornisce isolamento gratuitamente.

Le asserzioni sono il modo in cui un test segnala un fallimento

Un ciclo di vita esiste per eseguire asserzioni. Assertions.assertEquals(expected, actual) lancia un AssertionFailedError quando i valori differiscono, il che interrompe quel singolo test (il suo @AfterEach viene comunque eseguito) e lo segna come fallito — gli altri test continuano. Vedere asserzioni JUnit per l'insieme completo dei metodi assert*.

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

@Test void example() {
  assertEquals(42, compute());
  assertTrue(isReady());
  assertThrows(IllegalArgumentException.class, () -> parse("bad"));
}

Un esempio pratico: tracciare il ciclo di vita a mano

Non è disponibile un runner JUnit in questo playground, quindi il programma seguente modella il ciclo di vita con codice JDK semplice: attiva gli hook nell'ordine di JUnit, confronta PER_METHOD (una nuova istanza per test) con PER_CLASS (un'unica istanza condivisa) e termina con un piccolo harness di auto-verifica nello spirito di assertEquals.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • Il blocco PER_METHOD stampa instance#1, instance#2, instance#3 per i tre test, dimostrando la regola predefinita di JUnit: una nuova istanza di test viene costruita per ogni metodo @Test, quindi nessun test può vedere lo stato modificato di un altro test.
  • In PER_METHOD ogni riga [TEST] riporta counter=1, mai 2 o 3. Ogni istanza ha ottenuto il proprio campo fresco, il che è il motivo per cui i test rimangono indipendenti dall'ordine di esecuzione — il vantaggio principale del ciclo di vita predefinito.
  • Il blocco PER_CLASS riutilizza instance#1 per tutti e tre i test, e il suo counter sale da 1 → 2 → 3. Con un'unica istanza condivisa, lo stato dei campi di istanza si propaga deliberatamente tra i test — utile per fixture condivise costose, pericoloso se lo si dimentica.
  • @BeforeAll e @AfterAll compaiono ciascuno esattamente una volta per blocco, racchiudendo le coppie @BeforeEach/@AfterEach per test che si attivano tre volte — l'esatto ordine di annidamento che JUnit garantisce intorno ai test.
  • L'harness finale stampa PASS: per tutti e tre i controlli; un check fallito lancia un AssertionError con un messaggio FAIL:, rispecchiando il modo in cui Assertions.assertEquals interrompe un singolo test con un AssertionFailedError lasciando gli altri in esecuzione.

Capitoli correlati

Pratica

Pratica
In JUnit 5 con il ciclo di vita predefinito delle istanze di test, quante istanze di una classe di test contenente tre metodi @Test vengono create quando la classe viene eseguita?
In JUnit 5 con il ciclo di vita predefinito delle istanze di test, quante istanze di una classe di test contenente tre metodi @Test vengono create quando la classe viene eseguita?
Was this page helpful?