Tests d’architecture Java avec ArchUnit : Arrêtez de vous fier à votre diagramme et commencez à le tester

07 juil. 2026

Tests d’architecture Java avec ArchUnit : Arrêtez de vous fier à votre diagramme et commencez à le tester

Votre diagramme d’architecture indique que les contrôleurs n’accèdent jamais directement au dépôt. Votre code source n’est pas d’accord.

Cela se produit silencieusement. Quelqu’un fait preuve de rapidité, injecte une ligne directe de CourseRepository dans un contrôleur pour sauter une étape de service, et la demande de tirage est approuvée parce que les tests passent et que la modification est minime. Le diagramme dans le wiki montre encore un flux clair en trois couches. Or le code ne correspond plus à ce schéma. Six mois et quarante raccourcis plus tard, l’« architecture en couches » n’est plus qu’une suggestion, et personne ne peut dire quelles règles y restent valides.

L’organisation en couches n’est qu’une des formes que prend ce problème. Si vous organisez votre code par paquet plutôt que par couche, par fonctionnalité plutôt que par couche, la même usure se produit aux frontières entre les fonctionnalités: le paquet d’infrastructure partagée commence à importer des classes de fonctionnalités, les contrôleurs commencent à renvoyer des entités JPA au lieu de DTO, et ce qui devrait être un ensemble de limites claires se transforme silencieusement en confusion. La bonne nouvelle est que le même outil peut régler tous ces soucis, et vous n’avez pas besoin de paquets par couches pour imposer des règles de couches.

Le test d’architecture est la façon de combler cette lacune. Plutôt que de documenter vos règles dans un diagramme que personne ne lit, vous les encodez comme des tests exécutables qui échouent à la compilation dès que quelqu’un les viole. La règle cesse d’être une convention que chacun doit mémoriser et devient un capteur qui s’exécute à chaque commit.

Avant d’aller plus loin, il est important de faire la distinction suivante, qui se connecte à mes articles précédents sur l’ingénierie des harnais et les tests de mutation avec PIT : ArchUnit n’est pas une nouvelle catégorie de test que vous ajoutez comme un module complémentaire. Il s’agit d’une fonction d’aptitude, d’un capteur de rétroaction computationnelle qui observe votre base de code et vous informe si elle continue de respecter la structure que vous avez conçue. Alors que PIT protège le comportement de votre code, ArchUnit protège sa forme.

ArchUnit est la bibliothèque de tests d’architecture la plus utilisée sur la JVM. Il fonctionne comme des tests JUnit ordinaires, il n’a pas besoin d’un exécuteur dédié et lit les classes compilées pour déduire des paquets, des dépendances et de la nomenclature. Cet article montre comment configurer ArchUnit dans un projet Spring Boot avec Maven et JUnit 5, écrire des règles qui s’avèrent réellement utiles, l’adopter dans un code hérité sans vous noyader dans les violations et l’exécuter en CI.

Vous voulez le code ? Toutes les règles de cet article proviennent d’un projet réel, l’API Spring Boot de mon exemple CRUD full-stack : loiane/crud-angular-spring

Dans cet article, nous aborderons :

Pourquoi les diagrammes d’architecture se détériorent

Un diagramme répond à une question : quelle était notre intention avec la structure ? Mais il ne répond jamais à la question qui compte vraiment après six mois : quelle est la structure à présent ?

Le fossé est structurel, pas accidentel. Un diagramme est une capture figée prise durant la phase de conception. Le code change tous les jours. Rien ne relie les deux, alors ils s’éloignent dès que le premier raccourci est mis en œuvre. Le diagramme est une orientation prédictive sans capteur de rétroaction intégré; orientation sans capteur n’est qu’un espoir.

Considérons une règle de couche standard de Spring Boot : les contrôleurs dépendent des services, les services dépendent des dépôts et rien ne peut remonter. La règle dit qu’un contrôleur doit appeler un service et jamais accéder directement à un dépôt. Voici le raccourci qui rompt l’ordre, et aucun compilateur ni ensemble de tests ne l’empêche :

@RestController
public class CourseController {

    private final CourseRepository repository;

    public CourseController(CourseRepository repository) {
        this.repository = repository;
    }
}

Cela se compile. Les tests unitaires passent. La revue de code peut repérer le problème, si le relecteur connaît la règle et y prête attention un vendredi après-midi. Les tests d’architecture le détectent toujours, de façon déterministe, avant la fusion.

L’idée formelle vient d’une architecture évolutive : une fonction d’aptitude architecturale est une vérification objective et automatisée que telle caractéristique architecturale reste en place. Une règle de couches exprimée comme un test ArchUnit est une fonction d’aptitude. Elle transforme une règle que vous attendez que les développeurs suivent en une contrainte que la compilation impose.

Comment fonctionne ArchUnit

ArchUnit importe vos classes compilées et construit en mémoire un modèle de votre base de code : classes, paquets, méthodes, champs et les dépendances entre eux. Ensuite, vous écrivez des règles basées sur ce modèle à l’aide d’une API fluide, et chaque règle s’exécute comme un test JUnit ordinaire.

Il n’y a pas de réécriture de bytecode ni d’exécuteur séparé. ArchUnit lit les fichiers .class présents dans le classpath de test, voit donc exactement ce que le compilateur a produit, y compris les dépendances que vous n’avez pas écrites explicitement. Comme il s’agit simplement de JUnit, une violation d’architecture apparaît comme un test qui échoue dans le même rapport que tous les autres.

Trois notions à garder en tête d’avance :

Concept Signification
Importer ArchUnit analyse un ensemble de paquets et construit un modèle JavaClasses une fois par classe de test.
Règle Une expression fluide comme noClasses().that()...should()... qui tient ou échoue.
Violation Une paire précise de classe et de dépendance qui viole une règle, signalée avec la ligne exacte.

Contrairement aux tests de mutation, l’analyse d’architecture est économique. Importer quelques milliers de classes et évaluer une poignée de règles prend une ou deux secondes. Cette simple réalité déplace le capteur : PIT doit s’exécuter derrière un profil Maven lent, mais ArchUnit est suffisamment rapide pour s’exécuter dans la phase normale mvn test de chaque build. Plus de détails à ce sujet dans la section CI.

Configuration d’ArchUnit dans un projet Spring Boot

Le Projet d’Exemple

L’exemple est l’API Spring Boot de loiane/crud-angular-spring, l’application CRUD complète que j’utilise dans ce blog et dans mes vidéos. Le back-end réside dans le répertoire crud-spring et, intentionnellement, il n’est pas organisé par couches (paquets). Il est organisé par fonctionnalité : tout ce qui concerne les cours se regroupe dans un seul paquet, avec l’infrastructure partagée maintenue séparée.

crud-spring/src/
  main/java/com/loiane/
    course/                   the feature package
      Course.java  Lesson.java
      CourseController.java  CourseService.java  CourseRepository.java
      dto/         (records + MapStruct-style mapper)
      enums/       (Category, Status + JPA converters)
    exception/                business exceptions
    shared/                   controller advice, custom validators
  test/java/com/loiane/
    architecture/
      ArchitectureTest.java   every rule in this post

Cette structure est importante pour cet article. La plupart des tutoriels ArchUnit supposent l’existence de paquets tels que ..controller.., comme ..service.., ..repository.., dans lesquels vous pouvez définir des règles. Des bases de code réelles, organisées par paquet et par fonctionnalité, n’ont pas ces paquets. Les règles ci-dessous montrent comment imposer la même organisation par couches en utilisant des prédicats de classe — noms et annotations — plutôt que des noms de paquets. Si votre projet est organisé par paquet et par couche, les mêmes règles deviennent plus simples, et non plus complexes.

Pré-requis