Introduction
En rétrospective de sprint, quelqu'un pose la question : la règle ArchUnit qu'on vient d'ajouter, c'est un test unitaire ou un test d'intégration ? La discussion tourne un moment sans trancher, puis la règle finit reléguée dans une suite « lente », exécutée une fois par nuit avec les tests end-to-end. Trois semaines plus tard, une dépendance interdite se glisse dans le domaine un lundi matin, et personne ne le découvre avant le rapport du lendemain.
Pourquoi ça compte
La pyramide des tests classe les tests par coût et par portée : beaucoup de tests unitaires rapides à la base, moins de tests d'intégration au milieu, une poignée de tests bout en bout au sommet. C'est un excellent repère pour organiser des tests qui vérifient un comportement. Le problème commence quand on essaie d'y faire entrer, de force, un test qui ne vérifie pas un comportement du tout.
Ce que mesure vraiment un test d'architecture
Un test unitaire exécute du code et observe ce qu'il produit. Un test d'intégration fait la même chose à plus grande échelle, avec une vraie base de données ou un vrai appel réseau. Un test d'architecture, lui, n'exécute rien : il inspecte la structure du code compilé, comme l'expliquait l'article sur ArchUnit, qui analyse le bytecode plutôt que le code source. Il ne demande jamais « qu'est-ce que ce code produit ? », mais « où ce code a-t-il le droit de se trouver, et de quoi a-t-il le droit de dépendre ? ».
Cette question n'a pas d'étage dans la pyramide, parce que la pyramide entière parle de comportement. Un test d'architecture est orthogonal à cette hiérarchie, pas coincé entre deux de ses niveaux.
Le seul critère qui compte : la vitesse
Une fois qu'on arrête de chercher un étage, une question plus utile se pose : à quelle vitesse ce test s'exécute-t-il, et donc, à quelle fréquence peut-on se permettre de le lancer ?
Un test ArchUnit ne touche ni disque, ni réseau, ni base de données. Il charge des classes déjà compilées et parcourt leurs métadonnées : c'est une opération de quelques millisecondes par règle, du même ordre qu'un test unitaire.
ArchitectureTest.java
@AnalyzeClasses(packages = "com.compagnie.vols")
class ArchitectureTest {
@ArchTest
static final ArchRule domain_should_not_depend_on_infrastructure =
noClasses()
.that().resideInAPackage("..domain..")
.should().dependOnClassesThat()
.resideInAnyPackage("..infrastructure..");
}Rien dans cette classe ne justifie de la faire tourner une fois par nuit. Elle mérite exactement le traitement d'un test unitaire : dans le même module, dans la même commande mvn test, sur chaque commit, sur chaque pull request. Retarder son exécution ne la rend pas plus fiable, ça retarde seulement le moment où l'équipe apprend qu'une dépendance interdite vient d'apparaître.
Ce que ça change concrètement
Deux réflexes suffisent à corriger le tir. D'abord, faire tourner les règles ArchUnit dans le même module de test que les tests unitaires du domaine, sans profil Maven ou Gradle séparé qui les isolerait dans une exécution à part. Ensuite, les faire échouer la build au même titre qu'un test unitaire cassé, pas seulement produire un rapport consulté a posteriori.
Ce choix a une conséquence directe sur le rapport à l'erreur : une règle d'architecture violée est détectée à la compilation locale ou dans la première minute de CI, avant la revue de code, avant le merge, avant que quiconque d'autre n'ait à s'en soucier.
Une pyramide qui reste utile
Rien de tout ça ne rend la pyramide des tests obsolète. Elle continue de bien répondre à la question qu'elle a toujours posée : comment répartir l'effort de test sur des comportements, du plus isolé au plus englobant. Les tests d'architecture répondent simplement à une autre question, et n'ont jamais eu vocation à occuper l'un de ses étages.
Retour en rétrospective
La prochaine fois que la question revient en rétrospective, la réponse tient en une phrase : ce n'est ni un test unitaire, ni un test d'intégration, et ce n'est pas grave. Ce qui compte, c'est qu'il tourne aussi vite que le plus rapide des deux, et qu'il tourne aussi souvent. Le lundi matin où une dépendance interdite tente de se glisser dans le domaine, c'est cette vitesse d'exécution, bien plus que son étiquette, qui décide si elle est arrêtée tout de suite ou découverte trois semaines plus tard.
Continuer la lecture