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.