What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Un test unitaire vérifie rapidement et de façon isolée un comportement précis de votre code. Avec JUnit Jupiter, la partie moderne de JUnit 5, vous pouvez écrire ces tests, les exécuter dans votre IDE ou votre build Maven/Gradle, puis diagnostiquer les régressions avant qu’elles n’atteignent la production.

Ce premier volet explique la différence entre tests unitaires, d’intégration et end-to-end, puis montre comment structurer, paramétrer et fiabiliser des tests Java.

Qu’est-ce qu’un test unitaire ?

Un test unitaire vérifie une unité de comportement : souvent une classe, une méthode ou une règle métier. Il doit être rapide, répétable, déterministe, lisible et indépendant des autres tests autant que possible.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Le test porte sur un comportement observable, pas obligatoirement sur chaque ligne ni sur chaque méthode privée. Il ne faut donc pas rendre une méthode public uniquement pour pouvoir la tester : testez plutôt l’API publique et ses effets observables.

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class CalculatorTest {
    @Test
    void additionneDeuxNombres() {
        Calculator calculator = new Calculator();

        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }
}

Un test exécuté par JUnit n’est pas automatiquement un test unitaire. JUnit fournit l’infrastructure d’exécution ; c’est le périmètre, les dépendances et le niveau d’isolation qui déterminent la nature du test.

Unitaire, intégration ou end-to-end ?

Type Périmètre Exemple Dépendances réelles
Unitaire Une unité de comportement Calculer une remise Généralement non
Intégration Plusieurs composants ou une infrastructure Tester un repository avec PostgreSQL Oui, au moins partiellement
End-to-end Un parcours complet Passer une commande depuis l’API jusqu’à la base Oui

Les tests unitaires détectent rapidement les régressions, documentent les règles métier, facilitent les refactorings et localisent plus précisément les erreurs. Ils ont aussi un coût : maintenance, risque de couplage à l’implémentation, faux sentiment de sécurité et fragilité si les dépendances sont mal isolées.

JUnit 5 et JUnit Jupiter : quelle différence ?

Dans la documentation officielle, « JUnit 5 » désigne un ensemble de trois sous-projets : JUnit Platform, JUnit Jupiter et JUnit Vintage.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • JUnit Platform fournit l’infrastructure de découverte et d’exécution.
  • JUnit Jupiter fournit les annotations, assertions et le moteur moderne.
  • JUnit Vintage permet notamment d’exécuter des tests JUnit 3 ou JUnit 4 sur la Platform.

Pour un nouveau projet, utilisez Jupiter. JUnit 4 n’a pas cessé de fonctionner : une migration progressive peut conserver des tests historiques, éventuellement avec Vintage. La version exacte de JUnit doit être alignée sur le dependency management du projet plutôt que copiée depuis un ancien tutoriel.

Préparer Maven ou Gradle

Maven

Le module agrégateur junit-jupiter est pratique pour commencer :

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
</dependency>

Selon le projet, la version peut déjà être gérée par un parent POM ou un BOM. Le moteur Jupiter doit être disponible et la version de Maven Surefire doit être compatible avec JUnit Platform.

mvn test
mvn -Dtest=CalculatorTest test
mvn -Dtest=CalculatorTest#additionneDeuxNombres test

Le dernier filtrage dépend de la version et de la configuration de Surefire.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Gradle

Avec le DSL Groovy :

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:<version>")
}

test {
    useJUnitPlatform()
}

Avec le DSL Kotlin :

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:<version>")
}

tasks.test {
    useJUnitPlatform()
}
./gradlew test
./gradlew test --tests CalculatorTest
./gradlew test --tests 'CalculatorTest.additionneDeuxNombres'

Avec le plugin Java, Gradle fournit notamment le source set test, la tâche test et son rattachement au cycle check. La configuration useJUnitPlatform() est indispensable pour exécuter Jupiter. Voir la documentation Gradle sur les tests Java.

Où placer les fichiers ?

src/
├── main/
│   └── java/com/example/Calculator.java
└── test/
    └── java/com/example/CalculatorTest.java

Conservez généralement le même package, utilisez des noms terminés par Test et nommez les méthodes selon le comportement attendu.

Structurer un test avec Arrange, Act, Assert

La structure Arrange/Act/Assert rend le scénario lisible :

  • Arrange : préparer les objets et les données ;
  • Act : appeler le comportement testé ;
  • Assert : vérifier le résultat.

La variante Given/When/Then exprime la même idée dans un vocabulaire plus comportemental.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@Test
void appliqueUneRemiseDeDixPourCent() {
    // Arrange
    PriceCalculator calculator = new PriceCalculator();

    // Act
    BigDecimal result = calculator.applyDiscount(
        new BigDecimal("100.00"),
        new BigDecimal("0.10")
    );

    // Assert
    assertEquals(new BigDecimal("90.00"), result);
}

« Une assertion par test » est une heuristique, pas une loi. Plusieurs assertions sont pertinentes lorsqu’elles décrivent le même résultat cohérent. En revanche, un test qui vérifie plusieurs comportements sans rapport devient difficile à diagnostiquer.

Annotations Jupiter essentielles

Annotation Usage
@Test Test standard
@BeforeEach, @AfterEach Préparation et nettoyage avant ou après chaque test
@BeforeAll, @AfterAll Préparation ou nettoyage une fois par classe
@DisplayName Nom lisible dans les rapports
@Disabled Désactivation temporaire, à justifier
@Tag Catégorisation et filtrage
@Nested Regroupement de scénarios
@ParameterizedTest Même scénario avec plusieurs entrées
@RepeatedTest Répétition contrôlée
class UserValidatorTest {
    private UserValidator validator;

    @BeforeEach
    void setUp() {
        validator = new UserValidator();
    }

    @Test
    void accepteUnEmailValide() {
        // ...
    }

    @AfterEach
    void tearDown() {
        // Nettoyage uniquement si nécessaire
    }
}

Une méthode @BeforeEach doit préparer un contexte clair. Elle ne doit pas cacher une logique complexe dont les tests dépendent sans la montrer.

Les assertions courantes

assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertNotNull(value);
assertSame(expectedReference, actualReference);
assertNotSame(first, second);
assertArrayEquals(expected, actual);
assertIterableEquals(expected, actual);

Utilisez toujours l’ordre expected, actual. Pour plusieurs vérifications liées, assertAll permet de collecter les erreurs :

assertAll(
    () -> assertEquals("Alice", user.name()),
    () -> assertEquals("[email protected]", user.email())
);

Un message calculé par Supplier<String> évite un travail inutile lorsque le test réussit :

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
assertEquals(expected, actual,
    () -> "Valeur inattendue pour " + input);

Un exemple métier complet avec les exceptions

import java.math.BigDecimal;
import java.math.RoundingMode;

public class DiscountCalculator {
    public BigDecimal apply(BigDecimal price, BigDecimal rate) {
        if (price == null || rate == null) {
            throw new IllegalArgumentException("Les valeurs sont obligatoires");
        }
        if (price.signum() < 0) {
            throw new IllegalArgumentException("Le prix ne peut pas être négatif");
        }
        if (rate.compareTo(BigDecimal.ZERO) < 0
                || rate.compareTo(BigDecimal.ONE) > 0) {
            throw new IllegalArgumentException("Le taux doit être compris entre 0 et 1");
        }

        return price.multiply(BigDecimal.ONE.subtract(rate))
            .setScale(2, RoundingMode.HALF_UP);
    }
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;

class DiscountCalculatorTest {
    private final DiscountCalculator calculator = new DiscountCalculator();

    @Test
    void appliqueLeTauxDeRemise() {
        BigDecimal result = calculator.apply(
            new BigDecimal("100.00"), new BigDecimal("0.20"));

        assertEquals(new BigDecimal("80.00"), result);
    }

    @Test
    void accepteUnTauxNul() {
        BigDecimal result = calculator.apply(
            new BigDecimal("100.00"), BigDecimal.ZERO);

        assertEquals(new BigDecimal("100.00"), result);
    }

    @Test
    void rejetteUnTauxSuperieurAUn() {
        assertThrows(IllegalArgumentException.class, () ->
            calculator.apply(new BigDecimal("100.00"), new BigDecimal("1.01")));
    }

    @Test
    void rejetteUnPrixNegatif() {
        assertThrows(IllegalArgumentException.class, () ->
            calculator.apply(new BigDecimal("-1.00"), new BigDecimal("0.10")));
    }
}

assertThrows vérifie qu’une exception du type attendu est levée. Vous pouvez récupérer l’exception pour contrôler son message :

IllegalArgumentException exception = assertThrows(
    IllegalArgumentException.class,
    () -> calculator.apply(new BigDecimal("100.00"), new BigDecimal("1.01"))
);

assertEquals("Le taux doit être compris entre 0 et 1",
    exception.getMessage());

Ne vérifiez le message que s’il fait partie du contrat utile. Limitez aussi la lambda à l’opération susceptible d’échouer. La préparation ne doit pas être incluse par inadvertance, sinon une exception provenant de la fixture pourrait faire réussir le test pour la mauvaise raison.

Pour vérifier qu’aucune exception n’est levée, un appel normal suffit généralement : une exception inattendue fait échouer le test. assertDoesNotThrow peut être utilisé si cela rend l’intention plus explicite.

Attention à BigDecimal

BigDecimal.equals tient compte de l’échelle : new BigDecimal("80.0") n’est pas égal à new BigDecimal("80.00") avec equals. compareTo peut en revanche considérer les deux valeurs numériquement égales. Pour les montants, définissez une convention d’échelle et faites correspondre l’assertion au contrat métier.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Tests paramétrés et cas limites

Un test paramétré évite de copier le même scénario pour plusieurs valeurs :

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

class PalindromeTest {
    @ParameterizedTest
    @ValueSource(strings = {"radar", "kayak", "ressasser"})
    void reconnaitUnPalindrome(String value) {
        assertTrue(Palindrome.isPalindrome(value));
    }
}

Les sources utiles comprennent @NullSource, @EmptySource, @NullAndEmptySource, @EnumSource, @CsvSource, @MethodSource et @ArgumentsSource.

@ParameterizedTest
@CsvSource({
    "2, 3, 5",
    "0, 0, 0",
    "-2, 2, 0"
})
void additionneDeuxValeurs(int a, int b, int expected) {
    assertEquals(expected, calculator.add(a, b));
}

Chaque invocation possède son propre cycle @BeforeEach/@AfterEach. Les types doivent être convertibles depuis les paramètres fournis. Utilisez @MethodSource si le scénario nécessite des objets complexes. Une table trop longue ou obscure est un signal qu’il faut peut-être séparer les comportements.

Rendre les tests déterministes

Les tests ne doivent pas dépendre de l’heure, du hasard, du réseau, d’un port disponible, d’un fichier laissé par un autre test, de l’ordre d’exécution, de la locale ou du fuseau horaire de la machine.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Un code qui appelle directement Instant.now() est difficile à tester :

public boolean isExpired(Instant expiration) {
    return Instant.now().isAfter(expiration);
}

Injectez plutôt une horloge :

public class SessionService {
    private final Clock clock;

    public SessionService(Clock clock) {
        this.clock = clock;
    }

    public boolean isExpired(Instant expiration) {
        return Instant.now(clock).isAfter(expiration);
    }
}
Clock fixedClock = Clock.fixed(
    Instant.parse("2026-01-01T00:00:00Z"),
    ZoneOffset.UTC
);

La même approche s’applique aux générateurs aléatoires, UUID, variables d’environnement, fichiers temporaires, appels HTTP et configurations globales. Si un test ne passe qu’en suite complète, cherchez un état statique partagé, un cache non vidé, des données persistantes, une fuite de ressources ou le parallélisme. Un sleep masque généralement le problème au lieu de le résoudre.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Faut-il déjà utiliser Mockito ?

Non. Une dépendance locale, rapide et déterministe peut souvent être utilisée telle quelle. L’injection de dépendances et de petits objets réels produisent parfois des tests plus simples que des mocks.

Les termes ne sont pas interchangeables :

  • stub : fournit une réponse prédéfinie ;
  • mock : objet contrôlé dont les interactions peuvent notamment être vérifiées ;
  • spy : enveloppe autour d’un objet réel ;
  • fake : implémentation simplifiée mais fonctionnelle.

Mockito sert au mocking, au stubbing et à la vérification d’interactions. Exemple minimal :

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
class OrderServiceTest {
    @Test
    void enregistreLaCommande() {
        OrderRepository repository = mock(OrderRepository.class);
        OrderService service = new OrderService(repository);

        service.save(new Order("A-123"));

        verify(repository).save(any(Order.class));
    }
}

Utilisez un mock lorsqu’une dépendance est lente, externe, porteuse d’effets de bord ou doit être vérifiée comme interaction. Trop de mocks rendent les tests fragiles et peuvent faire réussir un scénario irréaliste. Vérifier des appels internes peut aussi coupler le test à l’implémentation. Une classe nécessitant de nombreux stubs est parfois trop complexe.

Exécuter les tests dans l’IDE et la CI

  1. Créez la classe sous src/test/java.
  2. Ajoutez JUnit Jupiter au projet.
  3. Lancez une méthode ou une classe depuis votre IDE.
  4. Exécutez toute la suite avec mvn test ou ./gradlew test.
  5. Consultez le rapport d’échec.
  6. Reproduisez localement avec la classe ou la méthode ciblée.
  7. Corrigez le code ou le test selon la cause réelle.

La CI doit au minimum lancer le même build que le développeur. Gradle produit des rapports de tests et propose des mécanismes de filtrage et de journalisation ; Maven s’appuie généralement sur Surefire pour le goal test.

Diagnostic des problèmes fréquents

Aucun test détecté

  • Vérifiez l’emplacement src/test/java.
  • Utilisez org.junit.jupiter.api.Test, et non une annotation JUnit 4 importée par erreur.
  • Vérifiez la présence du moteur Jupiter.
  • Avec Gradle, vérifiez useJUnitPlatform().
  • Avec Maven, vérifiez la version et la configuration de Surefire.
  • Contrôlez le nom de la classe et les conventions d’inclusion du build.

NoSuchMethodError ou erreur de version

Recherchez un mélange de versions Platform/Jupiter, un moteur absent, une dépendance transitive ancienne ou un plugin de build incompatible. Inspectez l’arbre plutôt que d’ajouter des JAR au hasard :

mvn dependency:tree
./gradlew dependencies

Test vert, comportement incorrect

Vérifiez qu’il existe réellement une assertion, qu’elle est assez précise et qu’elle couvre une donnée réaliste. Un mock configuré pour retourner exactement la valeur attendue peut cacher un défaut. Ajoutez aussi des scénarios d’erreur et des cas limites.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test intermittent

Inspectez l’heure courante, le hasard, le parallélisme, les threads, les temporisations, l’état global, les ressources non fermées et les accès réseau. Un test unitaire ne devrait généralement pas attendre un service externe.

Couverture de code : un indicateur, pas une note

La couverture mesure quelles portions de code ont été exécutées. Elle ne mesure pas la qualité des assertions ni la pertinence des scénarios. Un taux élevé peut coexister avec des assertions faibles, des tests uniquement nominaux, des erreurs non vérifiées ou des tests qui valident un détail d’implémentation.

Ne poursuivez pas automatiquement « 100 % partout ». Priorisez les règles métier, les chemins d’échec, les cas limites et les comportements qui présentent le plus de risque. Les tests unitaires ne prouvent pas que la base de données, la configuration, les contrats HTTP, les performances ou le parcours complet fonctionnent ensemble.

Maven, Gradle et les outils autour de JUnit

Maven privilégie des conventions explicites et une configuration XML. Gradle offre des DSL Groovy ou Kotlin et une logique de build plus flexible. Aucun des deux ne change les concepts JUnit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Les assertions JUnit suffisent pour débuter. AssertJ peut ensuite apporter des assertions fluides pour les collections et objets, tandis que Hamcrest propose des matchers composables. Ajoutez ces bibliothèques lorsqu’elles améliorent réellement la lisibilité.

Un IDE comme IntelliJ IDEA, Eclipse ou Visual Studio Code peut lancer et déboguer les tests, mais aucun IDE n’est obligatoire : Maven et Gradle suffisent pour l’exécution locale et la CI.

La méthode à retenir

  1. Définissez le comportement à vérifier.
  2. Gardez le test local, déterministe et indépendant.
  3. Structurez-le en Arrange, Act, Assert.
  4. Utilisez les assertions Jupiter et testez les erreurs.
  5. Paramétrez les cas similaires sans rendre la table illisible.
  6. Injectez l’horloge, le hasard et les autres sources d’instabilité.
  7. Lancez le test isolé, puis toute la suite dans le build.
  8. Traitez la couverture comme un signal, jamais comme une preuve de qualité.

Les étapes suivantes pourront approfondir Mockito et l’isolation des dépendances, les tests d’intégration, les repositories, la couverture, les tests de contrats, la CI/CD et le TDD.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.