عند إنشاء البرامج ، نتفق جميعًا ، كفريق واحد ، على اتباع مجموعة من الإرشادات التي تعتبر بشكل عام أفضل الممارسات. ومع ذلك ، أثناء التطوير ، قد يخالف المطورون هذه القواعد عن غير قصد أو عن غير قصد. عادة ما نعتمد على مراجعات الكود أو أدوات فحص جودة الكود مثل SonarQube و PMD وما إلى ذلك. للتحقق من وجود مثل هذه الانتهاكات. ولكن قد تكون بعض التوصيات حلولًا لا يمكن أتمتتها باستخدام SonarQube و PMD وما إلى ذلك.
على سبيل المثال ، عادةً ما أرغب في اتباع الإرشادات أدناه لتطبيقاتي القائمة على Java:
اتبع هيكلًا من ثلاث طبقات (الويب ، الخدمة ، طبقات المستودع) حيث يمكن لأي طبقة أن تتفاعل فقط مع الطبقة الدنيا المباشرة ، ويجب ألا تتفاعل الطبقة الدنيا مع الطبقة العليا. أولئك. يمكن لطبقة الويب التفاعل مع طبقة الخدمة ، ويمكن لطبقة الخدمة أن تتفاعل مع طبقة المستودع. لكن طبقة المستودع لا يمكنها الاتصال بالخدمة أو طبقة الويب ، ولا يمكن لطبقة الخدمة التفاعل مع طبقة الويب.
إذا كان التطبيق كبيرًا ، فقد نرغب في اتباع بنية Package-By-Feature ، حيث تكون مكونات الويب والخدمة فقط عامة ويجب أن تكون بقية المكونات خاصة بالحزمة.
عند استخدام Spring Dependency Injection ، لا تستخدم الحقن الميداني وتفضل الحقن المستند إلى المنشئ.
وبالتالي ، قد يكون هناك العديد من القواعد التي نريد اتباعها. الخبر السار هو أنه يمكننا التحقق من تنفيذ هذه التوصيات باستخدام اختبارات JUnit باستخدام ArchUnit .

هنا دليل مستخدم ArchUnit .
دعونا نرى كيف يمكننا استخدام ArchUnit لاختبار إرشادات البنية الخاصة بنا.
أضف التبعية التالية archunit-junit5 .
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>دعنا نلقي نظرة على كيفية تطبيق الإرشادات المختلفة التي ذكرتها أعلاه.
القاعدة 1. يجب ألا تتفاعل الخدمات والمستودعات مع طبقة الويب.
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);
}, .
يرجى قراءة دليل مستخدم ArchUnit الرسمي ومعرفة الأشياء الرائعة التي يمكنك القيام بها باستخدام ArchUnit .