Bei der Erstellung von Software verpflichten wir uns alle als Team, eine Reihe von Richtlinien zu befolgen, die im Allgemeinen als Best Practices gelten. Während der Entwicklung können Entwickler diese Regeln jedoch unwissentlich oder unfreiwillig brechen. Wir verlassen uns normalerweise auf Codeüberprüfungen oder Codequalitätsprüfer wie SonarQube , PMD usw. auf solche Verstöße zu prüfen. Einige der Empfehlungen können jedoch Lösungen sein, die mit SonarQube, PMD usw. nicht automatisiert werden können.
Beispielsweise möchte ich normalerweise die folgenden Richtlinien für meine Java-basierten Anwendungen befolgen:
Folgen Sie einer dreistufigen Struktur (Web-, Service-, Repository-Ebenen), in der jede Ebene nur mit der unmittelbaren unteren Ebene interagieren kann und die untere Ebene nicht mit der oberen Ebene interagieren sollte. jene. Die Webschicht kann mit der Serviceebene interagieren, die Serviceebene kann mit der Repository-Ebene interagieren. Die Repository-Schicht kann jedoch nicht mit dem Dienst oder der Web-Schicht kommunizieren. Die Service-Schicht kann nicht mit der Web-Schicht interagieren.
Wenn die Anwendung groß ist, möchten wir möglicherweise der Package-By-Feature- Struktur folgen , bei der nur die Web- und Servicekomponenten öffentlich sind und die übrigen Komponenten paketprivat sein müssen.
Verwenden Sie bei Verwendung der Spring Dependency Injection keine feldbasierte Injektion und bevorzugen Sie eine konstruktorbasierte Injektion.
Daher kann es viele Regeln geben, denen wir folgen möchten. Die gute Nachricht ist, dass wir die Umsetzung dieser Empfehlungen mithilfe von JUnit-Tests mit ArchUnit überprüfen können .

Hier führen Sie User ArchUnit .
Mal sehen, wie wir ArchUnit verwenden können, um unsere Architekturrichtlinien zu testen.
Fügen Sie die folgende Abhängigkeit archunit-junit5 hinzu .
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>Lassen Sie uns einen Blick darauf werfen, wie wir die verschiedenen oben genannten Richtlinien anwenden können.
Regel 1. Dienste und Repositorys sollten nicht mit der Webschicht interagieren.
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);
}, .
Bitte lesen Sie den offiziellen ArchUnit UserGuide und finden Sie heraus, welche großartigen Dinge Sie mit ArchUnit tun können .