Prueba unitaria de una arquitectura de proyecto Spring Boot con ArchUnit

Al crear software, los equipos de desarrollo suelen definir un conjunto de pautas y convenciones para la codificación que se consideran mejores prácticas.

Estos son métodos que generalmente se documentan y se comunican a todo el equipo de desarrollo que los adoptó. Sin embargo, durante el desarrollo, los desarrolladores pueden romper estas pautas, que se encuentran durante las revisiones del código o mediante verificadores de calidad del código.

Por lo tanto, es un aspecto importante automatizar estas directivas tanto como sea posible en toda la arquitectura del proyecto para optimizar las comprobaciones.

Podemos implementar estas pautas como pruebas JUnit verificables usando  ArchUnit . Esto asegura que la construcción de la versión del software se detenga en caso de una violación de la arquitectura.

ArchUnit   es una biblioteca gratuita, simple y extensible para probar la arquitectura de su código Java para su uso en cualquier entorno simple de prueba de unidades Java. Es decir, ArchUnit puede verificar dependencias entre paquetes y clases, niveles y cortes, verificar dependencias circulares y mucho más. Para ello, analiza un código de bytes Java determinado e importa todas las clases en la estructura del código Java.  

ArchUnit , :

. ArchUnit.

ArchUnit JUnit 5, Maven Central: 

pom.xml

XML

<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>0.14.1</version>
    <scope>test</scope>
</dependency>

build.gradle

Groovy

dependencies { 
  testImplementation 'com.tngtech.archunit:archunit-junit5:0.14.1' 
} } 

Java

class ArchunitApplicationTests {

  private JavaClasses importedClasses;

  @BeforeEach
  public void setup() {
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
    }

  @Test
  void servicesAndRepositoriesShouldNotDependOnWebLayer() {

      noClasses()
                .that().resideInAnyPackage("com.springboot.testing.archunit.service..")
                .or().resideInAnyPackage("com.springboot.testing.archunit.repository..")
                .should()
                .dependOnClassesThat()
                .resideInAnyPackage("com.springboot.testing.archunit.controller..")
                .because("Services and repositories should not depend on web layer")
                .check(importedClasses);
    }
}

-.

class ArchunitApplicationTests {

  private JavaClasses importedClasses;

  @BeforeEach
    public void setup() {
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
    }
    @Test
    void serviceClassesShouldOnlyBeAccessedByController() {
        classes()
                .that().resideInAPackage("..service..")
                .should().onlyBeAccessed().byAnyPackage("..service..", "..controller..")
                .check(importedClasses);
    }
}

ArchUnit API-, DSL, , , .  .

( AspectJ Pointcuts). 

Java

class ArchunitApplicationTests {
  
  private JavaClasses importedClasses;

  @BeforeEach
  public void setup() {
    importedClasses = new ClassFileImporter()
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
  }

    @Test
    void serviceClassesShouldBeNamedXServiceOrXComponentOrXServiceImpl() {
        classes()
                .that().resideInAPackage("..service..")
                .should().haveSimpleNameEndingWith("Service")
                .orShould().haveSimpleNameEndingWith("ServiceImpl")
                .orShould().haveSimpleNameEndingWith("Component")
                .check(importedClasses);
    }

    @Test
    void repositoryClassesShouldBeNamedXRepository() {
        classes()
                .that().resideInAPackage("..repository..")
                .should().haveSimpleNameEndingWith("Repository")
                .check(importedClasses);
    }
    @Test
    void controllerClassesShouldBeNamedXController() {
        classes()
                .that().resideInAPackage("..controller..")
                .should().haveSimpleNameEndingWith("Controller")
                .check(importedClasses);
    }
}

— . , Service, Component . .

Java

class ArchunitApplicationTests {
  private JavaClasses importedClasses;

  @BeforeEach
  public void setup() {
      importedClasses = new ClassFileImporter()
              .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
              .importPackages("com.springboot.testing.archunit");
  }

  @Test
  void fieldInjectionNotUseAutowiredAnnotation() {

      noFields()
              .should().beAnnotatedWith(Autowired.class)
              .check(importedClasses);
  }
  @Test
  void repositoryClassesShouldHaveSpringRepositoryAnnotation() {
      classes()
              .that().resideInAPackage("..repository..")
              .should().beAnnotatedWith(Repository.class)
              .check(importedClasses);
  }
  @Test
  void serviceClassesShouldHaveSpringServiceAnnotation() {
      classes()
              .that().resideInAPackage("..service..")
              .should().beAnnotatedWith(Service.class)
              .check(importedClasses);
  }
}

API ArchUnit Lang Java.  , , .

class ArchunitApplicationTests {

	private JavaClasses importedClasses;

	@BeforeEach
  public void setup() {
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
    }
    @Test
    void layeredArchitectureShouldBeRespected() {

					layeredArchitecture()
                .layer("Controller").definedBy("..controller..")
                .layer("Service").definedBy("..service..")
                .layer("Repository").definedBy("..repository..")
                .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
                .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service")
                .check(importedClasses);
    }
}

Spring Boot , .

ArchUnit ofrece un conjunto de funciones para verificar si se cumple con su arquitectura en capas. Estas pruebas brindan garantías automáticas de que el acceso y el uso se mantienen dentro de los límites establecidos. Por lo tanto, puede escribir sus propias reglas. En este artículo, hemos descrito varias reglas. La documentación oficial de ArchUnit presenta muchas más posibilidades.

El código fuente completo de los ejemplos se puede encontrar en mi  repositorio de GitHub .




All Articles