06 - Pruebas de arquitectura: ArchUnit
Una prueba de arquitectura comprueba que el código respeta las reglas estructurales acordadas: qué capa puede depender de cuál, dónde están las clases, si hay ciclos de dependencia. ArchUnit analiza el bytecode Java y convierte esas reglas en pruebas que fallan cuando el código las rompe.
1. Por qué probar la arquitectura
El diagrama de clases del tema anterior muestra las dependencias; las pruebas de arquitectura las imponen automáticamente. Sin ellas, las reglas se cumplen mientras alguien las recuerda; con ellas, se comprueban en cada mvn test.
@AnalyzeClasses(packages = "com.example.library") class ArchitectureTest { @ArchTest void controllersDoNotDependOnPersistence() { noClasses().that().resideInAPackage("..controller..") .should().dependOnClassesThat() .resideInAPackage("..persistence..") .check(); } }
La anotación @AnalyzeClasses indica qué paquetes analizar y check() ejecuta la regla sobre las clases importadas.
2. Reglas de capas
ArchUnit describe la estructura por capas y sus dependencias permitidas:
@ArchTest void respectsLayeredArchitecture() { LayeredArchitecture layers = layeredArchitecture().consideringAllDependencies() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Persistence").definedBy("..persistence.."); layers.whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service") .checkClasses(); }
| Regla | Significado |
|---|---|
mayOnlyBeAccessedByLayers | Qué capas pueden usar esta capa |
mayNotBeAccessedByAnyLayer | Nadie la usa desde fuera; solo se accede hacia dentro |
3. Otras reglas útiles
// Los servicios terminan en Service classes().that().resideInAPackage("..service..") .should().haveSimpleNameEndingWith("Service") .check(); // Sin ciclos entre paquetes slices().matching("com.example.(**)").should().beFreeOfCyclicDependencies() .check(); // El dominio no depende del framework noClasses().that().resideInAPackage("..domain..") .should().dependOnClassesThat() .resideInAnyPackage("org.springframework..") .check();
| Tipo de regla | Qué controla |
|---|---|
| Dependencias entre paquetes | Límites de capa |
| Ciclos | Que el grafo de dependencias sea acíclico |
| Convenciones de nombres | Dónde vive cada tipo de clase |
4. Conexión con los diagramas
Las reglas de ArchUnit son la versión ejecutable de lo que se dibuja:
- El diagrama de clases documenta las dependencias diseñadas.
- El diagrama C4 de componentes muestra los módulos permitidos.
- ArchUnit comprueba que el código real se ajusta a ambos.