Published on

ArchUnit:轻松测试软件架构

Authors
  • avatar
    Name
    ReLive27
    Twitter

为什么要测试你的架构?

当项目变得更大,架构变得更加复杂。每个项目都有开发人员需要遵循的标准规则。 新开发人员加入,他们可能会在不知情的情况下违反架构约束。如果每个人都在他们认为合适的地方添加新代码,每一个变化都可能对任何其他组件产生不可预见的影响,代码库就会变得混乱。

当然,您可以让一名或多名经验丰富的开发人员担任架构师的角色,他们每周查看一次代码,找出违规行为并加以纠正。

问题是这需要人工干预,有时我们并不能发现所有问题。保护软件架构免遭破坏的最佳方式是采用自动化流程。

在本文中,我将展示解决此类问题的 ArchUnit 框架。您将看到典型的实际示例,以了解如何将此工具集成到您的项目中。

什么是 ArchUnit

ArchUnit 用于单元测试 Java 项目架构。它可以检查包和类、层和切片之间的依赖关系,检查循环依赖关系等等。通常,开发人员会建立通用模式。 当有人违反规则时,测试将失败。开发人员将看到有关该问题的信息。它确保代码库保持完整,并且每个人都遵循准则。

ArchUnit 演示

我们创建一个 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> 

我们创建一个 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");  

测试排除

有时某些类我们不想执行规则,例如 JUnit 测试类。

这可以通过以下方式轻松实现:

@AnalyzeClasses(packages = "com.relive", importOptions = {ImportOption.DoNotIncludeTests.class}) 

或者有一个你想忽略的类,因为规则不适用于它。我们可以创建一个自定义规则并导入它,如下所示:

@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");  }  }  } 

结论

现在你已经了解如何将 ArchUnit 测试框架集成到您的 Java 项目中。您还熟悉了一些应用中的常用规则。

如果你想了解更多关于 ArchUnit 的规则示例,你可以参考 ArchUnit 用户指南 。 我想这里可以让你更加深入研究。

与往常一样,本文中使用的源代码可在 GitHub 上获得。