Pull to refresh

Модульное тестирование архитектуры Spring Boot проекта с помощью ArchUnit

Reading time5 min
Views5.4K
Original author: Anicet Eric

При создании программного обеспечения команды разработчиков обычно определяют набор руководящих принципов и соглашений по разработке кода, которые считаются лучшими практиками.

Это методы, которые обычно документируются и доводятся до сведения всей команды разработчиков, которая их приняла. Однако во время разработки разработчики могут нарушить эти рекомендации, которые обнаруживаются во время ревью кода или с помощью инструментов проверки качества кода.

Поэтому важным аспектом является автоматизация этих директив в максимально возможной степени по всей архитектуре проекта, позволяющая оптимизировать проверки.

Мы можем реализовать эти рекомендации как проверяемые JUnit тесты с помощью ArchUnit. Это гарантирует, что сборка версии программного обеспечения будет прекращена в случае нарушения архитектуры.

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.

Tags:
Hubs:
+11
Comments3

Articles

Change theme settings