ArchUnitを使用したSpringBootプロジェクトアーキテクチャのユニットテスト

ソフトウェアを構築する場合、開発チームは通常、ベストプラクティスと見なされるコーディングの一連のガイドラインと規則を定義します。

これらは通常、文書化され、それらを採用した開発チーム全体に伝達される方法です。ただし、開発中に、開発者はこれらのガイドラインに違反する可能性があります。これらのガイドラインは、コードレビュー中またはコード品質チェッカーを通じて見つかります。

したがって、重要な側面は、チェックを最適化するために、プロジェクトアーキテクチャ全体でこれらのディレクティブを可能な限り自動化することです。

これらのガイドラインは、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