Unidade testando uma arquitetura de projeto Spring Boot com ArchUnit

Ao construir software, as equipes de desenvolvimento geralmente definem um conjunto de diretrizes e convenções para codificação que são consideradas as melhores práticas.

Esses são métodos que geralmente são documentados e comunicados a toda a equipe de desenvolvimento que os adotou. No entanto, durante o desenvolvimento, os desenvolvedores podem quebrar essas diretrizes, que são encontradas durante as revisões de código ou por meio de verificadores de qualidade de código.

Portanto, um aspecto importante é automatizar essas diretivas tanto quanto possível em toda a arquitetura do projeto para otimizar as verificações.

Podemos implementar essas diretrizes como testes JUnit verificáveis ​​usando  ArchUnit . Isso garante que a construção da versão do software seja interrompida no caso de uma violação de arquitetura.

ArchUnit   é uma biblioteca gratuita, simples e extensível para testar a arquitetura de seu código Java para uso em qualquer ambiente de teste de unidade Java simples. Ou seja, o ArchUnit pode verificar dependências entre pacotes e classes, níveis e fatias, verificar dependências circulares e muito mais. Ele faz isso analisando um determinado bytecode Java e importando todas as classes para a estrutura do 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 oferece um conjunto de funções para verificar se sua arquitetura em camadas é respeitada. Esses testes fornecem garantias automáticas de que o acesso e o uso são mantidos dentro dos limites definidos. Portanto, você pode escrever suas próprias regras. Neste artigo, descrevemos várias regras. A documentação oficial do ArchUnit apresenta muitas outras possibilidades.

O código-fonte completo dos exemplos pode ser encontrado em meu  repositório GitHub .




All Articles