Pasar al contenido principal
Cargando...

ArchUnit

Inspiring technology by Hunters

En un proyecto, una arquitectura bien estructurada es uno de los pilares fundamentales para realizar un desarrollo sostenible en el tiempo. Hoy, nuestros Hunters nos comparten en este Inspiring Technology todo lo que debemos saber para conseguirlo: conoce ArchUnit.

Además de definir una arquitectura es necesario asegurarse que se aplica en el proyecto. Al inicio, se suele realizar la implementación acorde con la arquitectura planteada pero, con el paso del tiempo, se incrementa la complejidad de las funcionalidades, se añaden nuevos desarrolladores y se retiran desarrolladores experimentados; lo cual suele causar que el proyecto se desvíe de ella. Comprobar que se cumple con la arquitectura en cada cambio y desarrollo que se realiza dentro de proyecto es una tarea manual realizada por personas con un amplio conocimiento. Habitualmente, por falta de tiempo, estas revisiones no se pueden realizar de forma exhaustiva y ocupan tiempo que podría dedicarse a realizar otras revisiones más concretas.

ArchUnit es una librería que permite definir pruebas automatizadas para asegurar que un proyecto respeta la arquitectura con la que fue planteado. Estas pruebas se definen de forma sencilla y se ejecutan de forma rápida como pruebas JUnit. Además, simplifica las revisiones de código permitiendo centrarse en aspectos más específicos y, a su vez, ayuda a los desarrolladores con menos experiencia a comprender la arquitectura del proyecto pudiendo consultar en cualquier momento si los cambios que han realizado son correctos desde este punto de vista. Para su funcionamiento se definen reglas arquitecturales con la API de ArchUnit. Durante la realización de las pruebas se analiza el Bytecode generado y se comprueba que se cumplen las reglas.

Integración en un proyecto

ArchUnit se integra en la ejecución de pruebas, ya que posee una implementación de forma nativa con JUnit permitiendo adoptarlo de forma rápida y sencilla en la mayoría de proyectos. Tiene soporte explicito para JUnit 4 y JUnit 5, aunque también es posible su utilización con cualquier framework de automatización de pruebas que se ejecute en la máquina virtual de Java.

Para empezar, es necesario añadir la librería como dependencia del proyecto.

Dependencia de JUnit con gestor de proyectos Maven.

Dependencia de JUnit con gestor de proyectos Maven.

Una vez incluida la librería, se definen las pruebas automatizadas como pruebas JUnit con las siguientes peculiaridades:

  • Se indica el paquete base que contiene las clases a verificar, con la anotación @AnalyzeClasses.
  • Se indican los métodos de pruebas, con la anotación @ArchTest en vez de @Test.
  • Los métodos de prueba anotados con @ArchTest reciben como parámetro un objeto de tipo JavaClasses, que contendrá referencias a las clases sobre las que se realizarán las pruebas.
Estructura prueba ArchUnit con JUnit

Estructura prueba ArchUnit con JUnit.

Comprobaciones arquitecturas comunes

ArchUnit ofrece una serie de reglas predefinidas para facilitar la comprobación del correcto uso de arquitecturas en n-capas, arquitecturas onion o hexagonales. Para ilustrar estas reglas, se toma un proyecto de ejemplo planteado con una arquitectura en n-capas, donde la capa Presentation accede a la capa Service y esta, a su vez, accede a la capa Persistence. En este caso, queda restringido, por ejemplo, el acceso a la capa de Presentación por parte de métodos de la capa Service o Persistence.

Diagrama con definición de relación entre capas de ejemplo

Diagrama con definición de relación entre capas de ejemplo.

Para asegurar que se cumplen estas restricciones se define la siguiente prueba:

Diagrama con definición de relación entre capas de ejemplo

Diagrama con definición de relación entre capas de ejemplo.

.

En este cuadro se nombran cada una de las capas y se indica en qué paquete residen. Después se definen las restricciones que se han de cumplir entre capas:

  • Ninguna capa puede acceder a la capa Presentation.
  • La capa Service únicamente podrá ser accedida por la capa Presentation.
  • La capa Persistence únicamente podrá ser accedida por la capa Service.

De esta forma, si no se cumple alguna de estas restricciones, como podría ser en el caso de que un método de la capa de servicio dependiera de un método de la capa de presentación, esta prueba automatizada fallará.

API ArchUnit

ArchUnit ofrece una API sencilla que permite una definición clara de reglas arquitecturales para la definición de pruebas.

Para la definición de una regla se selecciona a qué unidad se aplica, permitiendo elegir entre clases, métodos, constructores, miembros, campos y las versiones negadas de cada de estas (por ejemplo, noClasses). A continuación, se introducen los filtros que se deseen establecer y, finalmente, las comprobaciones.

En el siguiente ejemplo se comprueba que ninguna clase que no resida en el paquete services, persistence o mappers dependa de clases que residen paquete entities.

Ejemplo definición de prueba

Ejemplo definición de prueba.

Caso de uso estructura de servicios

ArchUnit ofrece herramientas para definir reglas ajustadas a las especificaciones del proyecto. En el ejemplo mostrado a continuación se implementan las siguientes reglas referentes a los servicios de un proyecto realizado con Spring Boot:

  • Se definen las interfaces de los servicios en el paquete services y las implementaciones de estas dentro del paquete services.impl.
  • El código cliente siempre dependerá de la interfaz del servicio, nunca del servicio en sí mismo.
  • Dentro del paquete services.impl únicamente residen clases de servicio.
  • En ningún otro paquete de la aplicación residen más clases de servicio.
Ejemplo prueba paquete únicamente compuesto por interfaces

Ejemplo prueba paquete únicamente compuesto por interfaces.

Para asegurar que se cumplen las restricciones, primero se define una regla que comprueba que en el paquete services únicamente se definen interfaces.

Ejemplo prueba paquete únicamente compuesto por clases

Ejemplo prueba paquete únicamente compuesto por clases.

Se define una regla para asegurar que en ningún punto se depende directamente de clases que residen en service.impl. De esta forma, se dependerá de las interfaces service y será el framework el encargado de inyectar la clase correspondiente.

Ejemplo prueba no se depende de clases de servicio

Ejemplo prueba no se depende de clases de servicio.

.

Se define una regla para asegurar que el paquete service.impl está compuesto exclusivamente por servicios, es decir clases anotadas con _@Service_.

Ejemplo prueba paquete únicamente compuesto por clases

Ejemplo prueba paquete únicamente compuesto por clases.

.

Finalmente, se define una regla para asegurarse de que no existe ninguna clase anotada con _@Service_ fuera del paquete service.impl.

Ejemplo prueba servicios fuera de paquete services.impl

Ejemplo prueba servicios fuera de paquete services.impl.

 

Mensajes de error descriptivos

En el caso de incumplirse una regla, ArchUnit se encarga de generar un texto de error legible para ayudar a la compresión del problema que se ha causado. Además, se puede asociar un texto a una regla para indicar explícitamente lo motivación de esta.

En la siguiente prueba de ejemplo, se define una regla que comprueba si existe alguna clase anotada con @RestController fuera del paquete controllers.

Ejemplo prueba servicios fuera de paquete services.impl

Ejemplo prueba servicios fuera de paquete services.impl.

 

A continuación, se muestra un mensaje de error producido al fallar esta regla. Para esta prueba se ha añadido la anotación @RestController a la clase ClientDTO que está fuera del paquete controllers.

Ejemplo mensaje de error

Ejemplo mensaje de error.

 

Como se puede apreciar, ArchUnit crea un texto explicando la regla y, a continuación, se muestra el texto con la motivación. Al final del mensaje de error se muestra explícitamente dónde y qué problema ha ocurrido.

ArchUnit en proyectos C#

ArchUnit puede ejecutarse en cualquier proyecto que utilice lenguajes basados en la máquina virtual de Java, como son Kotlin, Scala, etc.

Para permitir su uso en proyectos que utilicen el lenguaje C# existe un fork llamado ArchUnitNet.

Ventajas

  • Permite asegurar cumplimiento de la arquitectura definida para el proyecto de forma automatizada.
  • Ayuda a personas nuevas en el proyecto a comprender la arquitectura.
  • Simplifica las revisiones de código.
  • Compatible con lenguajes basados en máquina virtual Java.
  • Ejecución rápida de pruebas.
  • Definición de pruebas sencilla.

Desventajas

  • ArchUnit analiza bytecode, complicando pruebas sobre generadores de código como Lombok o MapStruct.

Conclusiones

El uso de ArchUnit puede tener un gran impacto dentro de un proyecto, asegurando que se cumple con la arquitectura planteada, lo que facilita la revisión de código a los desarrolladores experimentados y facilita la introducción de nuevos desarrolladores. Además, se puede implementar de forma sencilla como pruebas unitarias integradas dentro del proyecto.

A continuación, podemos ver un vídeo de su funcionamiento: 

 

¿Quieres saber más sobre Hunters?

Ser un hunter es aceptar el reto de probar nuevas soluciones que aporten resultados diferenciales. Únete al programa Hunters y forma parte de un grupo transversal con capacidad de generar y transferir conocimiento.

Anticípate a las soluciones digitales que nos harán crecer. Consulta más información sobre Hunters en la web.

LinkedIn Joan Galiana

Joan Galiana

Técnico de Software en Altia