Integrando ArchUnit con SonarQube

Publicado por Víctor Madrid el

Arquitectura de SolucionesArchUnitSonarQube

En este artículo extra de la serie "Validando una Arquitectura con ArchUnit" se va a enseñar a integrar ArchUnit con SonarQube para poder darle más "potencia" y "visibilidad" a nuestra auditoría de Arquitectura.

A modo de recordatorio pongo los enlaces a los artículos anteriores:

Muchos me habéis preguntado sobre la integración con SonarQube y he querido contestar con un artículo para que entendáis que pasa con SonarQube según el tipo de implementación de ArchUnit que utilicéis en los tests. Y todo esto porque NO en todos los casos se comportan del todo "bien" con las versiones "actuales" de ArchUnit y SonarQube.

El objetivo es ahorraros tiempo y alguna lagrimita si le dedicáis esfuerzo a hacerlo bien (con vuestra solución) y queréis que salga en SonarQube bien representado. :-)

Este artículo está dividido en 5 partes:

  • 1. Premisas de ArcUnit sobre la Arquitectura a Validar
  • 1.1. Premisas de JUnit 5 respecto a ArchUnit
  • 1.2. Premisas del diseño de Reglas Arquitectónicas respecto a ArchUnit
  • 1.2.1. Enfoque basado en Implementaciones
  • 1.2.2. Enfoque basado en Anotaciones
  • 1.3. Premisas de Maven respecto a ArchUnit
  • 1.3.1. SIN los plugins de soporte Surefire y Failsafe
  • 1.3.2. CON los plugins de soporte Surefire y Failsafe
  • 1.4. Premisas del IDE respecto a ArchUnit
  • 2. Premisas de ArcUnit sobre SonarQube
  • 2.1. Premisas sobre los resultados
  • 2.2. Planteamiento de los tests de Arquitectura
  • 3. Stack Tecnológico
  • 4. Ejemplos de Uso
  • 4.1. Análisis de los diferentes test de ArchUnit a utilizar y su comportamiento
  • 4.2. Análisis de los diferentes test de ArchUnit del proyecto con SonarQube
  • 4.3. Análisis de los resultados del análisis con SonarQube
  • 5. Conclusiones

1. Premisas de ArchUnit sobre la Arquitectura a Validar

1.1. Premisas de JUnit 5 respecto a ArchUnit

Según la documentación oficial de ArchUnit, el framework es capaz de trabajar con cualquier frameworks de testing para Java. Pero presenta ciertos módulos específicos de integración directa con las versiones 4 y 5 de JUnit :

  • archunit-junit4
  • archunit-junit5-api
  • archunit-junit5-engine
  • archunit-junit5-engine-api

Por lo que utilizar estas dependencias / librerías te asegura una integración "perfect" si usas JUnit... lógico.

Hay que tener en cuenta que cada versión de JUnit define una forma de integrarse, por lo que se aconseja leer la documentación si quieres que los tests te funcionen bien.

1.2. Premisas del diseño de Reglas Arquitectónicas respecto a ArchUnit

Existen diferentes enfoques a la hora desarrollar las Reglas Arquitectónicas con ArchUnit :

  • Enfoque basado en Implementaciones
  • Enfoque basado en Anotaciones

1.2.1. Enfoque basado en Implementaciones

Se hace uso de las implementaciones de las clases proporcionadas por el framework, donde cada una de ella definirá como configurarse y sobre todo cómo y dónde usarse.

Ejemplos de implementaciones de Clases : ClassFileImporter, ArchRule, ArchRules...

Ejemplo de "Clase con Enfoque basado en Implementaciones"

//@RunWith(ArchUnitRunner.class) // Important: Only for JUnit 4 and not needed JUnit5
public class ClassFileImporterWithImplementationTest {  
    private static final String GENERIC_PACKAGE_TEST_VALUE = "com.acme.example";

    private JavaClasses IMPORTED_CLASSES = new ClassFileImporter()
        .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
        .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)
        .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
        .importPackages(GENERIC_PACKAGE_TEST_VALUE);

@Test
public void rule1() {  
        List<?> importedClassesList = ArchUnitComponentUtil.generarJavaClassList(IMPORTED_CLASSES);

        assertNotNull(importedClassesList);
        assertEquals(NUM_IMPORTED_CLASSES_DEFAULT_GENERIC_WITH_OPTIONS, importedClassesList.size());
}

}

Enfoque basado en Anotaciones

Se hace uso de las anotaciones proporcionadas por el framework, donde cada una de ella definirá como configurarse y sobre todo cómo y dónde usarse

Algunas de estas anotaciones estarán asociadas a implementaciones de clases específicas del framework.

Ejemplos de anotaciones : @AnalyzeClasses, @ArchTest, etc...

Ejemplo de "Clase con Enfoque basado en Anotaciones"

//@RunWith(ArchUnitRunner.class) // Important: Only for JUnit 4 and not needed JUnit5
@AnalyzeClasses(packages = "com.acme.example", 
    importOptions = {       
ImportOption.DoNotIncludeTests.class,  
ImportOption.DoNotIncludeJars.class,  
ImportOption.DoNotIncludeArchives.class  
}
)
public class ClassFileImporterWithAnnotationTest {  
    // ArchRules can just be declared as static fields and will be evaluated
    @ArchTest
    public static final ArchRule rule1 = classes().should()...

    @ArchTest
    public static void rule2(JavaClasses classes) {
        // The runner also understands static methods with a single JavaClasses argument
        // reusing the cached classes
    }
}

Se puede aplicar sobre atributos y/o métodos estáticos.

1.3. Premisas de Maven respecto a ArchUnit

Importante
Se aconseja que todos los desarrolladores de un proyecto utilicen la misma versión de Maven, con vistas a evitar sustos cuando se hacen cosas un poco más complicadas o requieren configuraciones peculiares
Hay que recordar que CI / CD también se puede ver afectado por la versión de Maven.

1.3.1. SIN los plugins de soporte Surefire y Failsafe

Cuando se ejecuta la fase de test de Maven SIN incluir ninguno de los plugins pueden pasar muchos problemas, pero aquí hoy hablaremos del Problema de las volumetrías iguales a 0.

Este problema se producen cuando se ejecutan los tests de JUnit 5 desde Maven y en el reporting de consola se puede ver que :

  • Se identifican las clases de test que componen el proyecto
  • Se muestran valores 0  en los test que componen las clases a nivel de volumetrías : Run, Failures, Errors y Skipped
  • NO se incluyen referencias a los plugins Maven Surefire y Maven Failsafe en el POM
  • Los tests realmente NO se ejecutan

Ejemplo de "Ejecución de un install desde Maven con el Problema de las Volumetrías iguales a 0"

Se debe a que la versión de Maven <=3.6.0 incluyen los plugins de Maven Surefire y Maven Failsafe ambos en versiones <2.22.0 y que tienen sólo soporte para JUnit 4.

Si se quiere resolver, se tendría que añadir las dependencias correspondientes a junit-plataform-surefire-provider y junit-jupiter-engine en las versiones adecuadas dentro de la definición del propio plugin.

JUnit 5 requiere los plugins de Maven Surefire y Maven Failsafe en versiones >=2.22.0

Para evitar lios aconsejo seguir los siguienes pasos :

Paso 1: Actualizar (si se puede) la versión de Maven si esta es muy antigua -> Esto normalmente NO se hace mucho en la vida real...jejeje

Paso 2:  Añadir/Actualizar la versión de los plugins de Surefire y Failsafe a versiones >=2.22.0

Ejemplo de "Ejecución de un install desde Maven con Plugins en versión correcta"

Se puede ver quee ahora que se identifican los tests, se pueden visualizar sus volumetrías y sobre todo que realmente estan funcionando.

1.3.2. CON los plugins de soporte Surefire y Failsafe

Cuando se ejecuta la fase de test de Maven incluyendo los plugins pueden pasar muchos problemas, pero aquí hoy hablaremos del Problema de determinar el origen de la información de una dependencia o plugin dentro de un proyecto Maven.

Este problema se producen cuando NO se tiene claro donde se realiza la definición del plugins y sus aspectos de configuración, pero estos están disponibles en el uso de forma "directa". Es decir, que nosotros no hemos referenciado nada pero aparecen.

Para ayudar a descubrirlo, aconsejo ejecutar el siguiente comando dentro de cada proyecto.

mvn help:effective-pom -Dverbose=true  

Este comando nos va a ayudar a determinar sobre el POM "efectivo" cuál ha sido el origen de "las partes de las cosas" que estamos utilizando.

Ejemplo de "Determinar el origen de Surefire en el proyecto SIN Spring Boot

Se puede ver que la versión viene definida por una dependencia en el propio proyecto, que otros aspectos toman valores de la dependencia de maven-core (org.apache.maven:maven-core:3.6.3) y que las cosas han sido sobreescritas en el propio proyecto hacen su referencia al mismo.

Ejemplo de "Determinar el origen de Surefire en el proyecto CON Spring Boot

Se puede ver que la versión viene definida por una dependencia con Spring Boot (org.springframework.boot:spring-boot-dependencies:2.3.4.RELEASE) que otros aspectos toman valores de la dependencia de maven-core (org.apache.maven:maven-core:3.6.3) y que las cosas han sido sobreescritas en el propio proyecto hacen su referencia al mismo.

1.4. Premisas del IDE respecto a ArchUnit

Cuando se lanzan los tests desde un IDE (Por ejemplo : Eclipse) es similar a hacerlo desde Maven. Aunque yo siempre aconsejo hacerlo desde línea de comandos porque a veces el IDE incorpora mucha magia en estas cosas.

Además NO vamos a instalar un IDE en Jenkins...jejeje (ya sabéis por donde voy)

Importante
Se aconseja revisar que versión de Maven se está utilizando desde el IDE, ya que algunos de ellos proporcionan una versión embebida que podría ser una versión diferente a la que se está utilizando por consola lo cual podría ser peligroso.

Si se utilizan las versiones adecuadas de los plugins Surefire y Failsafe se pueden ver que se identifican bien las clases de test utilizadas así como los tests que las componen, mostrando los resultados ya sean tests unitarios/integración y/o de ArchUnit

Ejemplo de "Ejecución de Tests Unitarios / Integración / ArchUnit desde Eclipse"

2. Premisas de ArcUnit sobre SonarQube

2.1. Premisas sobre el análisis de código sobre los tests de ArchUnit

Algun@ se habrá dado cuenta de que el IDE de algún aviso sobre el incumplimiento de la regla de SonarQube squid:S2187 cuando se define algunos "tipos" de tests de ArchUnit.

Esto pasa en aquellos IDEs más modernos o que tiene algún tipo de plugin instalado de integración con SonarQube u otras herramientas, en otros casos en este punto NO nos enteramos, aunque te aconsejo seguir leyendo para enterarse bien de lo que va a pasar y sobre todo los motivos.

¿Qué significa esto?

Según parece los tests que incumplen esta información es que han sido identificados como un TestCase de JUnit SIN tests en su interior, es decir, una clase de prueba, pero que en su interior NO se ha encontrado ningún test.

Además, está identificado como un "Code Smell" y tiene la criticidad de ser "Blocker", lo que quiere decir que aparecerá reflejado en el análisis de SonarQube claramente...jejeje.

Como se puede ver en la anterior imagen, el IDE puede proporcionar información sobre lo que está pasando. Pero mi consejo es ir a ver lo que dice la documentación de SonarQube (que debería ser la misma), para ello accedemos a la siguiente URL de detalle de la regla 2187 :

https://rules.sonarsource.com/java/RSPEC-2187  

Se puede ver que dice lo mismo que el mensaje del IDE, pero aquí ya aparece la indicación de que frameworks están soportados y entre ellos esta "ArchUnit", esto  lo tenemos que mirar con lupa porque no penséis que funciona para todos los casos.

Primero investigué mucho sobre el código y encontré ciertos "patrones" de funcionamiento ArchUnit-SonarQube que luego enseñaré y explicaré en la sección de ejemplos.

Por otro lado, inicie una investigación en los foros de SonarQube para ver si era al "único al que le pasaba" (todo un clásico en mi vida...jejeje) y encontré ciertas referencias al respecto.

Ejemplo de uno de los reportes que ha hecho la gente (este es de Diciembre del 2019)

https://community.sonarsource.com/t/s2187-reports-all-archunit-tests-as-empty-java/17703  

Casi todas las referencias que encontré hacían referencia a un bug declarado sobre SonarQube, pero hay más...

https://jira.sonarsource.com/browse/SONARJAVA-3263  

Este bug afecta sobre todo al uso de ArchUnit con atributos y debería estar resuelto en versiones del producto posteriores a la fecha de resolución 26/Feb/20.

Por lo que si usamos una versión de SonarQube anterior a esta fecha tendremos los problemas anteriormente contados, pero NO os preocupéis que trataremos de buscar una solución.

2.2. Planteamiento de los tests de Arquitectura

Recordemos que los tests de ArchUnit se utilizan para evaluar la arquitectura de una aplicación, pero NO tienen un implicación sobre la funcionalidad ,es decir, no se deberían de considerar tests unitarios al uso.

Por lo tanto, la ejecución de estos tests NO tienen relación directa con la cobertura de los tests, por lo que en la mayoría de los casos solamente tendrían una utilidad representativa en SonarQube. Es muy importante tener claro esto para tener un claro discurso a la hora de defender los datos.

Donde si tienen importancia es en el momento desarrollo, que es el punto exacto para encontrar cualquier cosa que NO cuadre según lo que este establecido :-)

Los tests unitarios / integración junto con los tests de arquitectura en el momento del desarrollo se convierten en el "arma" perfecta para abordar cualquier proyecto.

3. Stack Tecnológico

Este es el stack tecnológico elegido para implementar la funcionalidad de "Validando una Arquitectura con ArchUnit"

  • Java 8
  • Maven 3 - Gestor de dependencias
  • JUnit 5 - Framework de Testing Unitario
  • ArchUnit - Framework de Testing de Arquitectura
  • Docker - Tecnología de Contenedores/Containers
  • Docker Hub - Repositorio de Docker Público donde se ubican las imágenes oficiales
  • SonarQube 8 - Analizador de código estático

4. Ejemplos de Uso

Para enseñar a integrar ArchUnit con SonarQube y enseñar todos los problemas anteriores, he utilizado el repositorio específico y en concreto el proyecto "acme-api-greeting-model"

  • Incluye los plugins de Surefire y Failsafe en una versión válida -> así generar los informes adecuados de la ejecución de test
  • Incluye el plugin de Jacoco para determinar la cobertura y generar los informes adecuados

Por otro lado, se proporciona en el mismo repositorio un proyecto "docker-sonarqube" para ayudar a montar SonarQube basado en el uso de Docker.

4.1. Análisis de los diferentes test de ArchUnit a utilizar y su comportamiento

Para NO impactar "mucho" en los otros artículos relacionados se ha generado un nuevo paquete en "src/test/java" con nombre "com.acme.greeting.api.model.greeting.archunit.sonar" dentro del proyecto "acme-api-greeting-model".

En este paquete se han generado varios tipos de test para validar todas las "teorías" (posibilidades) teniendo en cuenta todas las implementaciones posibles :

  • CheckControlJUnit5Test : Test de control que representa la correcta ejecución de un test unitario básico con soporte de JUnit5
  • NO presenta problemas de "squid:S2187"
  • Contiene 1 Test
  • Genera informa de Surefire CON paquetería
  • CheckArchitectureOnlyUsesImplementationTest : Test de ArchUnit con la evaluación de las Reglas Arquitectónicas desde métodos "típicos" de test anotados con @Test y utilizando las implementaciones de clases para la carga y la evaluación proporcionadas por ArchUnit
  • NO presenta problemas de "squid:S2187"
  • Contiene 2 Tests
  • Genera informa de Surefire CON paquetería
  • CheckArchitectureOnlyUsesAnnotationArchTestOnAttributesArchRuleTest : Test de ArchUnit con la evaluación de las Reglas Arquitectónicas desde atributos estáticos anotados con @ArchTest y utilizando la carga  mediante la anotación @AnalyzeClasses
  • SÍ presenta problemas de "squid:S2187"
  • Contiene 2 Tests
  • Genera informa de Surefire SIN paquetería
  • CheckArchitectureOnlyUsesAnnotationArchTestOnMethodsTest : Test de ArchUnit con la evaluación de las Reglas Arquitectónicas desde métodos estáticos anotados con @ArchTest y utilizando la carga  mediante la anotación @AnalyzeClasses
  • NO presenta problemas de "squid:S2187"
  • Contiene 2 Tests
  • Genera informa de Surefire SIN paquetería
  • CheckArchitectureOnlyUsesAnnotationArchTestOnAttributesArchRulesGroupTest : Test de ArchUnit con la evaluación de las Reglas Arquitectónicas sobre un grupo desde atributos estáticos anotados con @ArchTest y utilizando la carga mediante la anotación @AnalyzeClasses
  • SÍ presenta problemas de "squid:S2187"
  • Contiene 8 Tests -> 1 Grupo con 8 Reglas
  • Genera informa de Surefire SIN paquetería
  • CheckArchitectureLayeredTest : Test de ArchUnit con la evaluación de una arquitectura de capas desde atributos estáticos anotados con @ArchTest y utilizando la carga mediante la anotación @AnalyzeClasses
  • SÍ presenta problemas de "squid:S2187"
  • Contiene 1 Test
  • Genera informa de Surefire SIN paquetería

Ejemplo de "Ejecución de Tests de ArcUnit Válidos desde Eclipse"

Ejemplo de "Ejecución de Tests de ArcUnit Inválidos desde Eclipse"

Ejemplo de "Ejecución de Tests de ArcUnit Inválidos desde Eclipse"

Ejemplo de "Informes de Surefire de todos los tests"

Nota : se incluyen los que ya tenía el proyecto

Como se puede ver en la imagen algunos de ellos contiene en el nombre la paquetería y otros no.

4.2. Análisis de los diferentes test de ArchUnit del proyecto con SonarQube

Montaremos SonarQube a partir de un fichero "docker-compose" proporcionado en el proyecto "docker-sonarqube".

Paso 1: Arrancamos SonarQube ubicándonos en la ruta del proyecto y ejecutando el siguiente comando :

docker-compose up --build  

Paso 2: Accedemos a la URL desde el navegador

http://localhost:9000  

Paso 3: Autenticamos con el siguiente usuario

Usuario : admin  
Password : admin  

Paso 4: Creamos un nuevo proyecto

  • Identificamos el nombre del proyecto en SonarQube con el mismo nombre del proyecto en el repositorio
Project key : acme-api-greeting-model  
Display name : acme-api-greeting-model  
  • Establecemos el valor del token (En mi caso uso el mismo nombre del proyecto para este ejemplo)
  • Verificamos que se ha generado el token
  • Continuamos y establecemos las características del proyecto hasta que se genera un comando de análisis

Paso 5: Ejecutamos el comando desde la línea de comandos

mvn sonar:sonar \  
  -Dsonar.projectKey=acme-api-greeting-model \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.login=ebc16b838646cc32907e8aeda3c843d5b6fbdff0

Nota: Uso otro identificador en el campo "sonar.login" con respecto al paso de verificar el token

Paso 6: Verificamos que se ha producido el análisis

4.3. Análisis de los resultados del análisis con SonarQube

En este punto nos vamos a centrar en dos aspectos :

  • Verificar "Code Smells"
  • Verificar "Tests"

Verificar "Code Smells"

Se puede apreciar en la imagen que todos los tests presenta un "Code Smell" común que es "Add some tests to this class". Este "Code Smell" es la descripción de la regla S2187...os suena :-)

http://localhost:9000/coding_rules?open=java%3AS2187&rule_key=java%3AS2187  
Nota
Recordar que se representan los tests de ArchtUnit que ya tenía el proyecto junto con los tests realizados para este análisis.

Los tests utilizados en nuestro análisis y que tienen el "Code Smell : Add some tests to this class" son :

  • CheckArchitectureLayeredTest.java
  • CheckArchitectureOnlyUsesAnnotationArchTestOnAttributesArchRuleTest.java
  • CheckArchitectureOnlyUsesAnnotationArchTestOnAttributesArchRulesGroupTest.java

Conclusiones :

Los tests que presentan el incumplimiento con la regla S2187 son los que se han definido mediante el uso de los atributos en la clase, lo quiere decir que existe algún tipo de anomalía a la hora de instrumentalizar estos atributos con las anotaciones @ArchTest, su representación mediante un método de test y su identificación por SonarQube.

Verificar "Tests"

Se puede apreciar en la imagen que todos los tests que son identificados por SonarQube mediante el informe de Surefire, presentando el número de test que lo componen.

Nota
Recordar que se representan los tests de ArchtUnit que ya tenía el proyecto junto con los test realizados para este análisis.

Los tests utilizados en nuestro análisis y que aparecen identificados son :

  • CheckArchitectureLayeredTest.java
  • CheckControlJUnit5Test.java
  • CheckArchitectureOnlyUsesAnnotationArchTestOnAttributesArchRuleTest.java
  • CheckArchitectureOnlyUsesAnnotationArchTestOnMethodsTest.java
  • CheckArchitectureOnlyUsesImplementationTest.java

Conclusiones :

Los tests que se presentan aquí son identificados por esta versión de SonarQube, lo que NO se puede asegurar es que hayan sido identificados por versiones ni anteriores ni posteriores...cada una es un mundo...jejeje

NO se ha presentado el test "CheckArchitectureOnlyUsesAnnotationArchTestOnAttributesArchRulesGroupTest.java" que se implementa con @ArchTest sobre un atributo estático y hace la ejecución de reglas en un grupo, aunque si se ha identificado con en la sección de "Code Smell".

Si queremos excluir algunos de ellos o bien todos según algún criterio podemos utilizar la opción de exclusión del análisis que proporciona el propio plugin de SonarQube en Maven -> sonar-maven-plugin

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>sonar-maven-plugin</artifactId>
        <version>${sonar.plugin.version}</version>
        <configuration>
            <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
            <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
            <sonar.language>java</sonar.language>
            <sonar.jacoco.itReportPath>
                ${basedir}/target/jacoco.exec
            </sonar.jacoco.itReportPath>
            <sonar.exclusions>
                **/test/**/archunit/*
            </sonar.exclusions>
        </configuration>
    </plugin>

5. Conclusiones

Aclaración sobre los artículos anteriores
Para ayudar a entender mejor el planteamiento de los ejemplos utilizados en los artículos anteriores, quiero hacer esta aclaración teniendo en cuenta las decisiones tomadas.
Las decisiones tomadas han sido :
  • Uso de tests de Arquitectura basados en el uso de anotaciones sobre atributos estáticos -> Incumplimiento de la regla S2187, pero claridad en la implementación y sobre todo en el mantenimiento
  • Agrupaciones de estos tests en Catálogos de Reglas Arquitectónicas -> Muchos incumplimientos de la regla S2187, pero mayor claridad en su implementación y mantenimiento ...jejeje
  • Ejecución en los proyectos mediante test de arquitectura sobre grupos -> Evalúan todas las reglas del tirón sin dejarse ninguna, NO son identificados por SonarQube y me ahorran mucho código
  • Me importan su validación en tiempo de desarrollo frente a tiempo de integración -> En lo referente a construcción
  • Prefiero que la cobertura y los tests que aparecen en SonarQube sean sólo test unitarios o de integración.
Todas estas decisiones tienen una consecuencia muy grande, que es que estos tests NO apareceren reflejados en SonarQube a nivel de presentación. Esto se debe al hecho de utilizar tests de grupos que debido a problema que tienen con SonarQube hacen que NO aparezcan en el análisis, aun así, en mi caso concreto, los excluiría del análisis porque como yo he dicho NO es métrica quiera que aparezca representada.
En cualquier caso si queremos que aparezcan en SonarQube habría que plantear cambiar la implementación para no ejecutarlo como grupos y con las características de uso como métodos para no meter ruido en SonarQube.
Por otro lado recordar que según la implementación que utilicéis lo podemos llenar todo de "Code Smell: "Add some tests to this class" , por lo que habría que realizar alguna implementación con métodos y no con atributos , además de tratar de evitar el uso de la anotación @ArchTest.

El resumen de todo lo que se analiza en el artículo es que :

  • Los tests de arquitectura no se deberían de considerar tests que afecten a la cobertura de un proyecto
  • Cualquier test implementado con ArchUnit genera un informe en Surefire SIN paquetería -> En algún caso esto podría provocar algunos problemas
  • Cualquier test implementado con ArchUnit y que haga uso de atributos estáticos + @ArchTest para su definición presenta el problema de la regla S2187 de SonarQube
  • Aparecerán reflejados como un "Code Smell : Add some tests to this class."
  • Esto aplica a reglas individuales, grupos y para el uso de capas
  • La identificación o no de tests dependerá de la versión de SonarQube
  • Los tests que hacen la ejecución de reglas mediante grupos NO aparecen reflejados en la sección test de SonarQube
  • Parece que es algo interno de ArchUnit con su integración con SonarQube
  • La exclusión de test de arquitectura en SonarQube dependerá configuración a nivel de proyecto (con sonar-maven-plugin)
  • Con las versiones actuales hay que pensar muy bien como si diseña una solución global

Por lo que parece que la mejor solución es algo con unas características muy concretas y que definiré en el siguiente ejemplo.

Ejemplo de "Propuesta de Clase Mejor Solución"

public class CheckArchitectureOnlyUsesImplementationTest {

    JavaClasses IMPORTED_CLASSES = new ClassFileImporter()
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
            .importPackages("com.acme.greeting.api.model.greeting");

@Test
    public void util_classes_no_have_setters() {

        ArchRule util_classes_no_have_setters = classes()
                .that().resideInAPackage("..util..")
                .should(new ClassesNoPublicSetterArchCondition());

        util_classes_no_have_setters.check(IMPORTED_CLASSES);
    }

    @Test
    public void constant_classes_should_have_names_ending_with_the_word_constant() {

        ArchRule rule = CatalogConstantArchitectureRule.constant_classes_should_have_names_ending_with_the_word_constant;
        rule.check(IMPORTED_CLASSES);

    }

    @Test
    public void constant_classes_should_be_final() {

        CatalogConstantArchitectureRule.constant_classes_should_be_final.check(IMPORTED_CLASSES);

    }

}

Que son clases de test normales en JUnit con métodos de test normales donde se utiliza la implementación de clases de ArchUnit.

  • La carga de clases puede ser global a todos los métodos o específica a nivel de método
  • La definición de la regla puede hacerse a nivel del método de test utilizado o bien definido de forma externa

La parte de grupos se puede "simular" con:

  • Definir en una clase de Arquitectura especial de "testing" todos los métodos test que se quieren disparar SIN incluir la carga de clases en el método -> Requiere propagar estas clases así que cuidado con el scope de "test" de las dependencias :-)
  • El test "invocador" definirá las clases cargadas de ArchUnit a nivel de todos los métodos -> Como atributo
  • El test "invocador" sólo puede extender de una única clase (regla de Java), por lo que lo haría de una clase que simule el grupo y así heredaría todos sus métodos públicos de test -> Por lo que NO se podría tener un "grupo de grupos"
  • El test "invocador" permite definir métodos propios de test que además puede definir su propia carga de clases

Ejemplo de "Propuesta de Clase de Test de Arquitectua Simulando Grupo"

public class CheckArchitectureTest extends TestingConstantCatalogTest {

    JavaClasses IMPORTED_CLASSES = new ClassFileImporter()
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
            .importPackages("com.acme.greeting.api.model.greeting");

    @Test
    public void method_test_specific() {


    }

}

Teniendo todas estas cosas claras ya hemos dado otro pasito en mejorar la calidad de nuestros proyectos , ahora añadiendo SonarQube en la ecuación.

Espero que os haya gustado mucho, pero sobre todo que os haya ayudado a aclarar estos posibles problemas. :-)

No dudéis en seguirnos en Twitter para estar al día de próximos posts.

Autor

Víctor Madrid

Líder Técnico de la Comunidad de Arquitectura de Soluciones en atSistemas. Aprendiz de mucho y maestro de nada. Técnico, artista y polifacético a partes iguales ;-)