使用ArchUnit对Spring Boot项目架构进行单元测试

在构建软件时,开发团队通常会定义一组编码准则和惯例,这些准则和惯例被视为最佳实践。

这些方法通常记录在案,并传达给采用这些方法的整个开发团队。但是,在开发过程中,开发人员可能会违反这些准则,这些准则可在代码审查期间或通过代码质量检查器找到。

因此,一个重要的方面是在整个项目体系结构中使这些指令尽可能自动化,以优化检查。

我们可以使用ArchUnit将这些准则实施为可验证的JUnit测试 这样可以确保在发生体系结构违规时停止构建软件版本。

ArchUnit  是一个免费,简单且可扩展的库,用于测试可在任何简单Java单元测试环境中使用的Java代码的体系结构。也就是说,ArchUnit可以检查包和类之间,级别和切片之间的依赖关系,检查循环依赖关系等等。它通过解析给定的Java字节码并将所有类导入Java代码结构来实现。  

ArchUnit , :

. ArchUnit.

ArchUnit JUnit 5, Maven Central: 

pom.xml

XML

<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>0.14.1</version>
    <scope>test</scope>
</dependency>

build.gradle

Groovy

dependencies { 
  testImplementation 'com.tngtech.archunit:archunit-junit5:0.14.1' 
} } 

Java

class ArchunitApplicationTests {

  private JavaClasses importedClasses;

  @BeforeEach
  public void setup() {
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
    }

  @Test
  void servicesAndRepositoriesShouldNotDependOnWebLayer() {

      noClasses()
                .that().resideInAnyPackage("com.springboot.testing.archunit.service..")
                .or().resideInAnyPackage("com.springboot.testing.archunit.repository..")
                .should()
                .dependOnClassesThat()
                .resideInAnyPackage("com.springboot.testing.archunit.controller..")
                .because("Services and repositories should not depend on web layer")
                .check(importedClasses);
    }
}

-.

class ArchunitApplicationTests {

  private JavaClasses importedClasses;

  @BeforeEach
    public void setup() {
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
    }
    @Test
    void serviceClassesShouldOnlyBeAccessedByController() {
        classes()
                .that().resideInAPackage("..service..")
                .should().onlyBeAccessed().byAnyPackage("..service..", "..controller..")
                .check(importedClasses);
    }
}

ArchUnit API-, DSL, , , .  .

( AspectJ Pointcuts). 

Java

class ArchunitApplicationTests {
  
  private JavaClasses importedClasses;

  @BeforeEach
  public void setup() {
    importedClasses = new ClassFileImporter()
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
  }

    @Test
    void serviceClassesShouldBeNamedXServiceOrXComponentOrXServiceImpl() {
        classes()
                .that().resideInAPackage("..service..")
                .should().haveSimpleNameEndingWith("Service")
                .orShould().haveSimpleNameEndingWith("ServiceImpl")
                .orShould().haveSimpleNameEndingWith("Component")
                .check(importedClasses);
    }

    @Test
    void repositoryClassesShouldBeNamedXRepository() {
        classes()
                .that().resideInAPackage("..repository..")
                .should().haveSimpleNameEndingWith("Repository")
                .check(importedClasses);
    }
    @Test
    void controllerClassesShouldBeNamedXController() {
        classes()
                .that().resideInAPackage("..controller..")
                .should().haveSimpleNameEndingWith("Controller")
                .check(importedClasses);
    }
}

— . , Service, Component . .

Java

class ArchunitApplicationTests {
  private JavaClasses importedClasses;

  @BeforeEach
  public void setup() {
      importedClasses = new ClassFileImporter()
              .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
              .importPackages("com.springboot.testing.archunit");
  }

  @Test
  void fieldInjectionNotUseAutowiredAnnotation() {

      noFields()
              .should().beAnnotatedWith(Autowired.class)
              .check(importedClasses);
  }
  @Test
  void repositoryClassesShouldHaveSpringRepositoryAnnotation() {
      classes()
              .that().resideInAPackage("..repository..")
              .should().beAnnotatedWith(Repository.class)
              .check(importedClasses);
  }
  @Test
  void serviceClassesShouldHaveSpringServiceAnnotation() {
      classes()
              .that().resideInAPackage("..service..")
              .should().beAnnotatedWith(Service.class)
              .check(importedClasses);
  }
}

API ArchUnit Lang Java.  , , .

class ArchunitApplicationTests {

	private JavaClasses importedClasses;

	@BeforeEach
  public void setup() {
        importedClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("com.springboot.testing.archunit");
    }
    @Test
    void layeredArchitectureShouldBeRespected() {

					layeredArchitecture()
                .layer("Controller").definedBy("..controller..")
                .layer("Service").definedBy("..service..")
                .layer("Repository").definedBy("..repository..")
                .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
                .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service")
                .check(importedClasses);
    }
}

Spring Boot , .

ArchUnit提供了一组功能来检查您的分层体系结构是否得到遵守。这些测试可自动保证访问和使用保持在您设置的限制内。因此,您可以编写自己的规则。在本文中,我们描述了一些规则。 ArchUnit的官方文档介绍了更多的可能性。

示例的完整源代码可以在我的GitHub存储库中找到 




All Articles