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