Ao construir software, todos nós, como equipe, concordamos em seguir um conjunto de diretrizes que geralmente são consideradas as melhores práticas. No entanto, durante o desenvolvimento, os desenvolvedores podem quebrar essas regras sem saber ou sem querer. Normalmente contamos com análises de código ou verificadores de qualidade de código, como SonarQube , PMD, etc. para verificar tais violações. Mas algumas das recomendações podem ser soluções que não podem ser automatizadas com SonarQube, PMD, etc.
Por exemplo, geralmente desejo seguir as diretrizes abaixo para meus aplicativos baseados em Java:
Siga uma estrutura de três camadas (web, serviço, camadas de repositório) onde qualquer camada pode interagir apenas com a camada inferior imediata, e a camada inferior não deve interagir com a camada superior. Essa. a camada da web pode interagir com a camada de serviço, a camada de serviço pode interagir com a camada do repositório. Mas a camada do repositório não pode se comunicar com o serviço ou a camada da web, a camada de serviço não pode interagir com a camada da web.
Se o aplicativo for grande, podemos querer seguir a estrutura Package-By-Feature, em que apenas os componentes da Web e de serviço são públicos e o restante dos componentes deve ser privado do pacote.
Ao usar Spring Dependency Injection, não use injeção baseada em campo e prefira injeção baseada em construtor.
Portanto, pode haver muitas regras que desejamos seguir. A boa notícia é que podemos verificar a implementação dessas recomendações usando testes JUnit usando ArchUnit .

Aqui guia o usuário ArchUnit .
Vamos ver como podemos usar o ArchUnit para testar nossas diretrizes de arquitetura.
Adicione a seguinte dependência archunit-junit5 .
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>
Vamos dar uma olhada em como podemos aplicar as várias diretrizes que mencionei acima.
Regra 1. Os serviços e repositórios não devem interagir com a camada da web.
package com.sivalabs.moviebuffs;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
class ArchTest {
@Test
void servicesAndRepositoriesShouldNotDependOnWebLayer() {
JavaClasses importedClasses = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.sivalabs.moviebuffs");
noClasses()
.that().resideInAnyPackage("com.sivalabs.moviebuffs.core.service..")
.or().resideInAnyPackage("com.sivalabs.moviebuffs.core.repository..")
.should()
.dependOnClassesThat()
.resideInAnyPackage("com.sivalabs.moviebuffs.web..")
.because("Services and repositories should not depend on web layer")
.check(importedClasses);
}
}
ArchUnit DSL, , . , , .
2:
SpringBoot , . , Web Config .
.
@Test
void shouldFollowLayeredArchitecture() {
JavaClasses importedClasses = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.sivalabs.moviebuffs");
layeredArchitecture()
.layer("Web").definedBy("..web..")
.layer("Config").definedBy("..config..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..repository..")
.whereLayer("Web").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Config", "Web")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")
.check(importedClasses);
}
3: Spring @Autowired
@Test
void shouldNotUseFieldInjection() {
JavaClasses importedClasses = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.sivalabs.moviebuffs");
noFields()
.should().beAnnotatedWith(Autowired.class)
.check(importedClasses);
}
4:
, , Service ..
@Test
void shouldFollowNamingConvention() {
JavaClasses importedClasses = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.sivalabs.moviebuffs");
classes()
.that().resideInAPackage("com.sivalabs.moviebuffs.core.repository")
.should().haveSimpleNameEndingWith("Repository")
.check(importedClasses);
classes()
.that().resideInAPackage("com.sivalabs.moviebuffs.core.service")
.should().haveSimpleNameEndingWith("Service")
.check(importedClasses);
}
5: JUnit 5
JUnit 5 . JUnit 4 (… Testcontainers … ), / JUnit4 , @Test , Assert .. .
JUnit 4 :
@Test
void shouldNotUseJunit4Classes() {
JavaClasses classes = new ClassFileImporter()
.importPackages("com.sivalabs.moviebuffs");
noClasses()
.should().accessClassesThat().resideInAnyPackage("org.junit")
.because("Tests should use Junit5 instead of Junit4")
.check(classes);
noMethods().should().beAnnotatedWith("org.junit.Test")
.orShould().beAnnotatedWith("org.junit.Ignore")
.because("Tests should use Junit5 instead of Junit4")
.check(classes);
}
, .
Por favor, leia o Guia do usuário oficial do ArchUnit e descubra o que você pode fazer com o ArchUnit .