Представьте себе ситуацию, когда ваш проект должен компилироваться в различных окружениях.
Теперь представьте, что не все тесты должны проходить в этих окружениях — кажому свой набор тестов.
И предпочтительней настроить выбор, какие тесты должны выполняться, в… файле 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/
Добавить комментарий