Test unitaire d'une architecture de projet Spring Boot avec ArchUnit

Lors de la création de logiciels, les équipes de développement définissent généralement un ensemble de directives et de conventions de codage considérées comme des meilleures pratiques.

Ce sont des méthodes qui sont généralement documentées et communiquées à toute l'équipe de développement qui les a adoptées. Cependant, pendant le développement, les développeurs peuvent enfreindre ces directives, qui se trouvent lors des révisions de code ou via les vérificateurs de qualité du code.

Par conséquent, un aspect important est d'automatiser ces directives autant que possible dans toute l'architecture du projet pour optimiser les contrôles.

Nous pouvons implémenter ces directives en tant que tests JUnit vérifiables à l'aide d'  ArchUnit . Cela garantit que la construction de la version du logiciel est arrêtée en cas de violation de l'architecture.

ArchUnit   est une bibliothèque gratuite, simple et extensible pour tester l'architecture de votre code Java pour une utilisation dans n'importe quel environnement de test unitaire Java simple. Autrement dit, ArchUnit peut vérifier les dépendances entre les packages et les classes, les niveaux et les tranches, vérifier les dépendances circulaires, et bien plus encore. Pour ce faire, il analyse un bytecode Java donné et importe toutes les classes dans la structure de code 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 propose un ensemble de fonctions pour vérifier si votre architecture en couches est respectée. Ces tests fournissent des assurances automatiques que l'accès et l'utilisation sont maintenus dans les limites que vous avez définies. Par conséquent, vous pouvez rédiger vos propres règles. Dans cet article, nous avons décrit plusieurs règles. La documentation officielle d'ArchUnit présente de nombreuses autres possibilités.

Le code source complet des exemples se trouve dans mon  référentiel GitHub .




All Articles