[Java] Внедрение рекомендаций по структуре кода с использованием ArchUnit (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
12-Ноя-2020 03:31

При создании программного обеспечения все мы, как команда, соглашаемся следовать набору рекомендаций, которые обычно считаются лучшими практиками. Но во время разработки разработчики могут нарушить эти правила по незнанию или нежеланию. Обычно мы полагаемся на ревью кода или инструменты проверки качества кода, такие как SonarQube , PMD и т.д. для проверки таких нарушений. Но некоторые из рекомендаций могут быть решениями, которые нельзя автоматизировать с помощью SonarQube, PMD и т.д.Например, обычно я хочу следовать приведенным ниже рекомендациям для моих приложений на основе Java:
  • Следуйте трехуровневой структуре (уровни веб, сервис, репозиторий), где любой уровень может взаимодействовать только с непосредственным нижним уровнем, а нижний уровень не должен взаимодействовать с верхним уровнем. т.е. веб-уровень может взаимодействовать с уровнем сервиса, уровень сервиса может взаимодействовать с уровнем репозитория. Но уровень репозитория не может взаимодействовать с сервисным или веб-уровнем, сервисный уровень не может взаимодействовать с веб-уровнем.
  • Если приложение большое, мы могли бы захотеть следовать структуре Package-By-Feature, где только компоненты Web и Service являются public, а остальные компоненты должны быть package-private.
  • При использовании внедрения зависимостей Spring не используйте внедрение на основе поля и предпочитайте внедрение на основе конструктора.
Таким образом, может быть много правил, которым мы хотим следовать. Хорошая новость заключается в том, что мы можем проконтролировать выполнение этих рекомендаций с помощью JUnit тестов с использованием ArchUnit.
Вот здесь руководство пользователя ArchUnit.Давайте посмотрим, как мы можем использовать ArchUnit для тестирования наших рекомендаций по архитектуре.Добавьте следующую зависимость archunit-junit5.
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>0.13.1</version>
    <scope>test</scope>
</dependency>
Давайте рассмотрим, как мы можем применять различные рекомендации, о которых я упоминал выше.Правило 1. Сервисы и репозитории не должны взаимодействовать с веб-уровнем.
package com.sivalabs.moviebuffs;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
class ArchTest {
    @Test
    void servicesAndRepositoriesShouldNotDependOnWebLayer() {
      JavaClasses importedClasses = new ClassFileImporter()
          .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
          .importPackages("com.sivalabs.moviebuffs");
      noClasses()
          .that().resideInAnyPackage("com.sivalabs.moviebuffs.core.service..")
            .or().resideInAnyPackage("com.sivalabs.moviebuffs.core.repository..")
          .should()
            .dependOnClassesThat()
            .resideInAnyPackage("com.sivalabs.moviebuffs.web..")
          .because("Services and repositories should not depend on web layer")
          .check(importedClasses);
    }
}
ArchUnit предоставляет DSL, который довольно очевиден, чтобы понять цель теста. Приведенный выше тестовый код читается как английское предложение, что позволяет легко понять, что мы тестируем.Правило 2: следует придерживаться многоуровневой архитектурыВ типичном приложении SpringBoot уровень сервиса зависит от уровня репозитория, а уровни веб и конфигурации зависят от уровня сервиса. Мы не хотим, чтобы уровень Web или Config напрямую взаимодействовал с уровнем репозитория.Мы можем проверить выполение этого ограничения с помощью следующего теста.
@Test
void shouldFollowLayeredArchitecture() {
  JavaClasses importedClasses = new ClassFileImporter()
          .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
          .importPackages("com.sivalabs.moviebuffs");
  layeredArchitecture()
      .layer("Web").definedBy("..web..")
      .layer("Config").definedBy("..config..")
      .layer("Service").definedBy("..service..")
      .layer("Persistence").definedBy("..repository..")
      .whereLayer("Web").mayNotBeAccessedByAnyLayer()
      .whereLayer("Service").mayOnlyBeAccessedByLayers("Config", "Web")
      .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")
      .check(importedClasses);
}
Правило 3: Spring @Autowired НЕ следует использовать с внедрением зависимости на основе поля
@Test
void shouldNotUseFieldInjection() {
    JavaClasses importedClasses = new ClassFileImporter()
          .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
          .importPackages("com.sivalabs.moviebuffs");
    noFields()
      .should().beAnnotatedWith(Autowired.class)
      .check(importedClasses);
}
Правило 4: следует соблюдать соглашение об именахМы хотим следовать некоторым соглашениям об именах, например, все имена классов сервиса должны оканчиваться на Service и т.д.
@Test
void shouldFollowNamingConvention() {
    JavaClasses importedClasses = new ClassFileImporter()
        .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
        .importPackages("com.sivalabs.moviebuffs");
    classes()
        .that().resideInAPackage("com.sivalabs.moviebuffs.core.repository")
        .should().haveSimpleNameEndingWith("Repository")
        .check(importedClasses);
    classes()
        .that().resideInAPackage("com.sivalabs.moviebuffs.core.service")
        .should().haveSimpleNameEndingWith("Service")
        .check(importedClasses);
}
Правило 5: следует использовать только JUnit 5Мы хотим использовать JUnit 5 в качестве нашей среды тестирования. Но зависимость JUnit 4 могла попасть в путь к классам как транзитивная зависимость (хм… Testcontainers … хм), и мы могли случайно импортировать классы/аннотации JUnit4 , такие как @Test , Assert и т.д. по ошибке.Мы можем ограничить использование классов JUnit 4 следующим образом:
@Test
void shouldNotUseJunit4Classes() {
    JavaClasses classes = new ClassFileImporter()
        .importPackages("com.sivalabs.moviebuffs");
    noClasses()
        .should().accessClassesThat().resideInAnyPackage("org.junit")
        .because("Tests should use Junit5 instead of Junit4")
        .check(classes);
    noMethods().should().beAnnotatedWith("org.junit.Test")
        .orShould().beAnnotatedWith("org.junit.Ignore")
        .because("Tests should use Junit5 instead of Junit4")
        .check(classes);
}
Я только что описал лишь некоторые из возможностей, и вы можете проявить себя как угодно творчески.Пожалуйста, прочтите официальный ArchUnit UserGuide и узнайте о том, какие крутые вещи вы можете делать с ArchUnit.
===========
Источник:
habr.com
===========

===========
Автор оригинала: Siva Prasad Reddy
===========
Похожие новости: Теги для поиска: #_java, #_luchshie_praktiki (лучшие практики), #_testirovanie (тестирование), #_java
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 14:54
Часовой пояс: UTC + 5