Tabla de Contenidos

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:

Las reglas de arquitectura no se negocian prueba a prueba: se acuerdan y se automatizan. Si una regla debe cambiar, se cambia la regla deliberadamente, no rompiéndola en silencio.