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 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 .