為什么要測試你的架構(gòu)?
當項目變得更大,架構(gòu)變得更加復雜痕慢。每個項目都有開發(fā)人員需要遵循的標準規(guī)則。
新開發(fā)人員加入涌矢,他們可能會在不知情的情況下違反架構(gòu)約束掖举。如果每個人都在他們認為合適的地方添加新代碼,每一個變化都可能對任何其他組件產(chǎn)生不可預見的影響娜庇,代碼庫就會變得混亂塔次。
當然,您可以讓一名或多名經(jīng)驗豐富的開發(fā)人員擔任架構(gòu)師的角色名秀,他們每周查看一次代碼励负,找出違規(guī)行為并加以糾正。
問題是這需要人工干預匕得,有時我們并不能發(fā)現(xiàn)所有問題继榆。保護軟件架構(gòu)免遭破壞的最佳方式是采用自動化流程。
在本文中汁掠,我將展示解決此類問題的 ArchUnit 框架略吨。您將看到典型的實際示例,以了解如何將此工具集成到您的項目中考阱。
什么是 ArchUnit
ArchUnit 用于單元測試 Java 項目架構(gòu)翠忠。它可以檢查包和類、層和切片之間的依賴關系乞榨,檢查循環(huán)依賴關系等等秽之。通常当娱,開發(fā)人員會建立通用模式。
當有人違反規(guī)則時政溃,測試將失敗趾访。開發(fā)人員將看到有關該問題的信息态秧。它確保代碼庫保持完整董虱,并且每個人都遵循準則。
ArchUnit 演示
我們創(chuàng)建一個 Spring Boot 項目申鱼,將 ArchUnit maven 依賴項添加到您的pom.xml:
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
我們創(chuàng)建一個 ArchUnitTest.java
的Java類愤诱,我們將把所有的測試都放在那里。
命名檢查測試
如果您有多個模塊捐友,您可以使用注釋 @AnalyzeClasses
指出要掃描的包:
@AnalyzeClasses(packages = "com.relive")
例如淫半,您可能希望 SpringBoot 項目中 Application 類名稱應為“SpringBootTestApplication”:
@ArchTest
public static final ArchRule application_class_name_should_be =
classes().that().areAnnotatedWith(SpringBootApplication.class)
.should().haveSimpleName("SpringBootTestApplication");
您可以檢查是否所有 Controller 類都具有后綴“Controller”:
@ArchTest
static ArchRule controllers_suffixed_should_be =
classes().that().resideInAPackage("..controller..")
.or().areAnnotatedWith(RestController.class)
.should().haveSimpleNameEndingWith("Controller")
.allowEmptyShould(true);
包位置測試
您可能想檢查實體類是否位于“entity”包中:
@ArchTest
static final ArchRule tablename_must_reside_in_a_entity_package =
classes().that().areAnnotatedWith(TableName.class)
.should().resideInAPackage("..entity..")
.as("TableName should reside in a package '..entity..'")
.allowEmptyShould(true);
同樣,您可以檢查配置類是否位于“config”包中:
@ArchTest
static final ArchRule configs_must_reside_in_a_config_package =
classes().that().areAnnotatedWith(Configuration.class)
.or().areNotNestedClasses()
.and().areAnnotatedWith(ConfigurationProperties.class)
.should().resideInAPackage("..config..")
.as("Configs should reside in a package '..config..'")
.allowEmptyShould(true);
注釋測試
所有配置類都應具有 @Configuration
或者 @ConfigurationProperties
其中之一:
@ArchTest
static ArchRule configs_should_be_annotated =
classes()
.that().resideInAPackage("..config..")
.and().areNotNestedClasses()
.should().beAnnotatedWith(Configuration.class)
.orShould().beAnnotatedWith(ConfigurationProperties.class);
圖層測試
所有 Service 層的 Java 類僅可以被 Controller 層訪問匣砖,Dao 層的 Java 類僅可以被 Service 層訪問:
@ArchTest
static ArchRule layer_inspection = layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Dao").definedBy("..dao..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Dao").mayOnlyBeAccessedByLayers("Service");
測試排除
有時某些類我們不想執(zhí)行規(guī)則科吭,例如 JUnit 測試類。
這可以通過以下方式輕松實現(xiàn):
@AnalyzeClasses(packages = "com.relive", importOptions = {ImportOption.DoNotIncludeTests.class})
或者有一個你想忽略的類猴鲫,因為規(guī)則不適用于它对人。我們可以創(chuàng)建一個自定義規(guī)則并導入它,如下所示:
@AnalyzeClasses(packages = "com.relive", importOptions = {ArchUnitTest.ExcludeControllerImportOption.class})
public class ArchUnitTest {
static class ExcludeControllerImportOption implements ImportOption {
@Override
public boolean includes(Location location) {
return !location.contains("SomeExcludedControllerClasses");
}
}
}
結(jié)論
現(xiàn)在你已經(jīng)了解如何將 ArchUnit 測試框架集成到您的 Java 項目中拂共。您還熟悉了一些應用中的常用規(guī)則牺弄。
如果你想了解更多關于 ArchUnit 的規(guī)則示例,你可以參考 ArchUnit 用戶指南 宜狐。
我想這里可以讓你更加深入研究势告。
與往常一樣,本文中使用的源代碼可在 GitHub 上獲得抚恒。