Настройка состава JUnit5 тестов с помощью application.properties

от автора

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

Теперь представьте, что не все тесты должны проходить в этих окружениях — кажому свой набор тестов.

И предпочтительней настроить выбор, какие тесты должны выполняться, в… файле application.properties — кажому тесту свой переключатель «вкл/выкл».

Звучит здорово, не правда ли?

Тогда добро пожаловать под кат, где мы все это и реализуем с помощью SpringBoot 2 и JUnit 5.

Предварительные настройки

Сперва давайте выключим JUnit 4, который поставляется в SpringBoot 2 по-умолчанию, и включим JUnit 5.

Для этого внесем изменения в pom.xml:

<dependencies>     <!--...-->     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-test</artifactId>         <scope>test</scope>         <exclusions>             <exclusion>                 <groupId>junit</groupId>                 <artifactId>junit</artifactId>             </exclusion>         </exclusions>     </dependency>     <dependency>         <groupId>org.junit.jupiter</groupId>         <artifactId>junit-jupiter-engine</artifactId>         <version>5.3.2</version>         <scope>test</scope>     </dependency>     <!--...--> </dependencies>

Предполагаемое решение

Мы хотим аннотировать каждый тест простой аннотацией со свойством, указывающим на то, включен ли тест или нет. Напомню, что значения этого свойства мы собираемся хранить в файле application.properties.

Аннотация

Создадим аннотацию:

@Retention(RetentionPolicy.RUNTIME) @ExtendWith(TestEnabledCondition.class) public @interface TestEnabled {     String property(); }

Обработка аннотации

Без обработчика аннотации не обойтись.

public class TestEnabledCondition implements ExecutionCondition {      @Override     public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {         Optional<TestEnabled> annotation = context.getElement().map(e -> e.getAnnotation(TestEnabled.class));          return context.getElement()                         .map(e -> e.getAnnotation(TestEnabled.class))                         .map(annotation -> {                             String property = annotation.property();                              return Optional.ofNullable(environment.getProperty(property, Boolean.class))                                     .map(value -> {                                         if (Boolean.TRUE.equals(value)) {                                             return ConditionEvaluationResult.enabled("Enabled by property: "+property);                                         } else {                                             return ConditionEvaluationResult.disabled("Disabled by property: "+property);                                         }                                     }).orElse(                                             ConditionEvaluationResult.disabled("Disabled - property <"+property+"> not set!")                                     );                         }).orElse(                                 ConditionEvaluationResult.enabled("Enabled by default")                         );     } }

Необходимо создать класс (без аннотации Spring-а @Component), который реализует интерфейс ExecutionCondition.

В этом классе необходимо реализовать один метод этого интерфейса — ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context).

Этот метод принимает контекст выполняемого JUnit теста и возвращает условие — должен ли тест быть запущен или нет.

Прочитать подробней по условное выполнение тестов JUnit5 можно в официальной документации.

Но как нам проверить значение свойства, которое прописано в application.properties в таком случае?

Получение доступа к контексту Spring из контекста JUnit

Вот таким образом мы можем получить окружение Spring, с которым был запущен наш JUnit тест, из ExtensionContext.

Environment environment = SpringExtension.getApplicationContext(context).getEnvironment();

Можете взглянуть на полный код класса TestEnabledCondition.

Создадим тесты

Давайте создадим несколько тестов и попробуем управлять их запуском:

@SpringBootTest public class SkiptestApplicationTests {      @TestEnabled(property = "app.skip.test.first")     @Test     public void testFirst() {         assertTrue(true);     }      @TestEnabled(property = "app.skip.test.second")     @Test     public void testSecond() {         assertTrue(false);     }  }

Наш application.properties файл при этом выглядит так:

app.skip.test.first=true app.skip.test.second=false

Итак…

Результат запуска:

Следующий шаг — отделим префиксы наших свойств в аннотацию класса

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

Создадим annotation для хранения префиксов — TestEnabledPrefix:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestEnabledPrefix {     String prefix(); }

Обработка и использование аннотации TestEnabledPrefix

Приступим к обработке новой аннотации.

Давайте создадим вспомогательный класс AnnotationDescription

С помощью этого класса мы сможем хранить имя свойства из application.properties и его значение.

public class TestEnabledCondition implements ExecutionCondition {      static class AnnotationDescription {         String name;         Boolean annotationEnabled;         AnnotationDescription(String prefix, String property) {             this.name = prefix + property;         }         String getName() {             return name;         }         AnnotationDescription setAnnotationEnabled(Boolean value) {             this.annotationEnabled = value;             return this;         }         Boolean isAnnotationEnabled() {             return annotationEnabled;         }     }      /* ... */ }

Нам этот класс пригодится, т.к. мы собираемся использовать lambda-выражения.

Создадим метод, который извлечет нам значение свойства «префикс» из аннотации класса TestEnabledPrefix

public class TestEnabledCondition implements ExecutionCondition {      /* ... */      private AnnotationDescription makeDescription(ExtensionContext context, String property) {         String prefix = context.getTestClass()                 .map(cl -> cl.getAnnotation(TestEnabledPrefix.class))                 .map(TestEnabledPrefix::prefix)                 .map(pref -> !pref.isEmpty() && !pref.endsWith(".") ? pref + "." : "")                 .orElse("");         return new AnnotationDescription(prefix, property);     }      /* ... */  }

И теперь проверим значение свойства из application.properties по имени, указанном в аннотации теста

public class TestEnabledCondition implements ExecutionCondition {      /* ... */      @Override     public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {         Environment environment = SpringExtension.getApplicationContext(context).getEnvironment();          return context.getElement()                 .map(e -> e.getAnnotation(TestEnabled.class))                 .map(TestEnabled::property)                 .map(property -> makeDescription(context, property))                 .map(description -> description.setAnnotationEnabled(environment.getProperty(description.getName(), Boolean.class)))                 .map(description -> {                     if (description.isAnnotationEnabled()) {                         return ConditionEvaluationResult.enabled("Enabled by property: "+description.getName());                     } else {                         return ConditionEvaluationResult.disabled("Disabled by property: "+description.getName());                     }                 }).orElse(                         ConditionEvaluationResult.enabled("Enabled by default")                 );      }  }

Полный код класса доступен по ссылке.

Использование новой аннотации

Теперь применим нашу аннотацию к тест-классу:

@SpringBootTest @TestEnabledPrefix(property = "app.skip.test") public class SkiptestApplicationTests {      @TestEnabled(property = "first")     @Test     public void testFirst() {         assertTrue(true);     }      @TestEnabled(property = "second")     @Test     public void testSecond() {         assertTrue(false);     }  }

Теперь наш код тестов стал чище и проще.

Хочу выразить благодарность пользователям reddit-а за их советы:

1) dpash за совет
2) BoyRobot777 за совет

P.S.

Статья является авторским переводом. Английский вариант опубликован в README.md файле рядом с кодом проекта.


ссылка на оригинал статьи https://habr.com/ru/post/464881/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *