W3docs

Cycle de vie des tests JUnit en Java

Cycle de vie des instances de test et comportement par méthode ou par classe dans JUnit 5.

Chaque test JUnit 5 s'exécute dans un cycle de vie bien défini : une séquence de hooks d'initialisation et de nettoyage qui se déclenchent autour de vos méthodes @Test dans un ordre garanti. Comprendre cet ordre — et la règle selon laquelle JUnit crée une nouvelle instance de la classe de test pour chaque méthode de test — est ce qui distingue des suites de tests fragiles et dépendantes de l'ordre d'exécution des suites propres et isolées. Ce chapitre présente les cinq annotations du cycle de vie et les deux modes de cycle de vie que JUnit propose.

Les cinq annotations du cycle de vie

JUnit 5 (le package org.junit.jupiter.api) définit quatre annotations de rappel qui encadrent vos tests, ainsi que @Test lui-même. Pour un tour d'horizon complet de chacune, consultez les annotations JUnit ; si vous débutez avec ce framework, commencez par l'introduction à JUnit.

AnnotationS'exécuteLa méthode doit être
@BeforeAllUne fois, avant tout test de la classestatic (dans le cycle de vie par défaut)
@BeforeEachAvant chaque méthode @Testinstance
@TestLe test lui-mêmeinstance
@AfterEachAprès chaque méthode @Testinstance
@AfterAllUne fois, après que tous les tests ont été exécutésstatic (dans le cycle de vie par défaut)

Une classe de test unique contenant trois tests déclenche donc @BeforeAll une fois, puis @BeforeEach@Test@AfterEach trois fois, puis @AfterAll une fois.

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"); }
}

Une nouvelle instance par méthode de test

La règle la plus importante du cycle de vie : par défaut, JUnit construit une toute nouvelle instance de la classe de test avant chaque méthode de test. Les champs que vous modifiez dans un test ne peuvent pas se propager dans un autre, car le test suivant s'exécute sur un objet différent. C'est ce qui rend les tests indépendants de l'ordre d'exécution.

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
}

Les deux tests voient counter == 1. Si JUnit réutilisait une seule instance, le second test observerait 2 et passerait ou échouerait selon l'ordre — exactement la fragilité que cette conception prévient.

PER_METHOD vs. PER_CLASS

Vous pouvez désactiver le comportement par instance par méthode avec @TestInstance(Lifecycle.PER_CLASS). JUnit crée alors une seule instance pour toute la classe, les champs d'instance persistent entre les tests, et — par commodité — @BeforeAll/@AfterAll peuvent ne pas être static.

AspectPER_METHOD (par défaut)PER_CLASS
Instances crééesune par @Testune par classe
État des champs d'instanceréinitialisé à chaque testpartagé entre les tests
@BeforeAll/@AfterAlldoivent être staticpeuvent être des méthodes d'instance
Idéal pourisolation maximaleinitialisation partagée coûteuse
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() { }
}

Utilisez PER_CLASS uniquement lorsque l'initialisation est véritablement coûteuse et sûre à partager. Le mode par défaut vous offre l'isolation gratuitement.

Les assertions permettent à un test de signaler un échec

Un cycle de vie existe pour exécuter des assertions. Assertions.assertEquals(expected, actual) lève une AssertionFailedError lorsque les valeurs diffèrent, ce qui interrompt ce test unique (son @AfterEach s'exécute tout de même) et le marque comme échoué — les autres tests continuent. Consultez les assertions JUnit pour la liste complète des méthodes assert*.

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

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

Un exemple concret : tracer le cycle de vie manuellement

Il n'y a pas de runner JUnit dans cet environnement de code, donc le programme ci-dessous modélise le cycle de vie en code JDK pur : il déclenche les hooks dans l'ordre de JUnit, compare PER_METHOD (une nouvelle instance par test) avec PER_CLASS (une instance partagée), et se termine par un petit harnais d'auto-vérification dans l'esprit de assertEquals.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Le bloc PER_METHOD affiche instance#1, instance#2, instance#3 pour les trois tests, prouvant la règle par défaut de JUnit : une nouvelle instance de test est construite pour chaque méthode @Test, donc aucun test ne peut voir l'état modifié d'un autre test.
  • Dans PER_METHOD, chaque ligne [TEST] affiche counter=1, jamais 2 ou 3. Chaque instance dispose de son propre champ fraîchement initialisé, ce qui explique pourquoi les tests restent indépendants de l'ordre d'exécution — le principal avantage du cycle de vie par défaut.
  • Le bloc PER_CLASS réutilise instance#1 pour les trois tests, et son counter monte de 1 → 2 → 3. Avec une seule instance partagée, l'état des champs d'instance se propage délibérément entre les tests — utile pour des fixtures partagées coûteuses, dangereux si on l'oublie.
  • @BeforeAll et @AfterAll apparaissent chacun exactement une fois par bloc, encadrant les paires @BeforeEach/@AfterEach par test qui se déclenchent trois fois — l'ordre d'imbrication exact que JUnit garantit autour de vos tests.
  • Le harnais final affiche PASS: pour les trois vérifications ; un check échoué lève une AssertionError avec un message FAIL:, reproduisant la façon dont Assertions.assertEquals interrompt un seul test avec une AssertionFailedError tout en laissant les autres s'exécuter.

Chapitres connexes

Pratique

Pratique
Dans JUnit 5 avec le cycle de vie d'instance de test par défaut, combien d'instances d'une classe de test contenant trois méthodes @Test sont créées lors de l'exécution de la classe ?
Dans JUnit 5 avec le cycle de vie d'instance de test par défaut, combien d'instances d'une classe de test contenant trois méthodes @Test sont créées lors de l'exécution de la classe ?
Was this page helpful?