Arch Unit: testear la arquitectura

En este post vamos a hablar sobre un tipo de pruebas del cual llevo oyendo hablar desde hace un año, las pruebas unitarias de arquitectura.

Introducción

Una organización quiere aplicar determinadas reglas o estilos de arquitectura a todos los proyectos desarrollados en java. Estas reglas podrían incluir desde la forma en la que se estructura el proyecto, a las librerías que se utilizan el naming que se aplica, anotaciones que se deben o no se deben utilizar, etc.

Supongamos que hay muchos equipos distintos, con mayor o menor experiencia, y queremos que todos apliquen las mismas reglas de arquitectura en sus proyectos. Esto podría suponer un seguimiento regular de sus desarrollos, con el problema de revisar grandes cantidades de código.

Corremos el riesgo de que la arquitectura no se aplique correctamente o como deseamos en la totalidad de los desarrollos.

Bien, en este artículo vamos a hablar de Arch Unit. Se trata de una librería para comprobar arquitecturas en código Java mediante un framework de test unitarios.

ArchUnit

ArchUnit is a free, simple and extensible library for checking the architecture of your Java code using any plain Java unit test framework. That is, ArchUnit can check dependencies between packages and classes, layers and slices, check for cyclic dependencies and more. It does so by analyzing given Java bytecode, importing all classes into a Java code structure. You can find examples for the current release at ArchUnit Examples and the sources on GitHub.

https://www.archunit.org/

La idea es definir una batería de pruebas y reglas sobre cómo debe ir implementada nuestra arquitectura que deberán pasar todos los proyectos. De esta forma “forzamos” a todos los proyectos a cumplir dichas reglas de arquitectura y no pasar por encima o depender de una revisión de código para que dichas reglas de arquitectura se cumplan.

El autor de esta librería es Peter Gafert y otros contribuidores. Aquí podemos ver una conferencia muy interesante del autor sobre ArchTest: https://www.youtube.com/watch?v=dDQk-YI8N-c

En la web del proyecto se ilustran varias aplicaciones posibles de esta librería:

Package Dependency Checks

noClasses().that().resideInAPackage("..source..")
    .should().dependOnClassesThat().resideInAPackage("..foo..")

classes().that().resideInAPackage("..foo..")
    .should().onlyHaveDependentClassesThat().resideInAnyPackage("..source.one..", "..foo..")

Class Dependency Checks

classes().that().haveNameMatching(".*Bar")
    .should().onlyHaveDependentClassesThat().haveSimpleName("Bar")

Class and Package Containment Checks

classes().that().haveSimpleNameStartingWith("Foo")
    .should().resideInAPackage("com.foo")

Inheritance Checks

classes().that().implement(Connection.class)
    .should().haveSimpleNameEndingWith("Connection")

classes().that().areAssignableTo(EntityManager.class)
    .should().onlyHaveDependentClassesThat().resideInAnyPackage("..persistence..")

Annotation Checks

classes().that().areAssignableTo(EntityManager.class)
    .should().onlyHaveDependentClassesThat().areAnnotatedWith(Transactional.class)

Layer Checks

layeredArchitecture()
    .layer("Controller").definedBy("..controller..")
    .layer("Service").definedBy("..service..")
    .layer("Persistence").definedBy("..persistence..")

    .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
    .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
    .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")

Cycle Checks

slices().matching("com.myapp.(*)..").should().beFreeOfCycles()

Además de definir nuestras propias reglas, existen reglas genéricas que se pueden aplicar. Por lo que he estado viendo es muy configurable y dispone de plugins tanto para maven como para gradle. La documentación muestra todas las posibles aplicaciones de esta libería.

Un ejemplo de Baeldung

En Baeldung podemos encontrar una poc de esta libería: https://www.baeldung.com/java-archunit-intro