Настройка состава 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 за совет


PS


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

Источник: https://habr.com/ru/post/464881/


Интересные статьи

Интересные статьи

Всем привет! В этой статье хочу поделиться одним из способов запуска тестов производительности JMeter в OpenShift'e с использованием Jenkins'a в качестве автоматизации. Сначала мы п...
В одной из предыдущих статей цикла про гипервизор Proxmox VE мы уже рассказывали, как выполнять бэкап штатными средствами. Сегодня покажем, как для этих же целей использовать отличн...
Привет, Хабр! Мы часто говорим о способах восстановления данных на магнитных и твердотельных накопителях, резервном копировании, создании RAID’ов и прочих ухищрениях, которые помогают не остаться...
Есть у меня один Python-скрипт с расчётами. Там был цикл примерно на 2000 итераций, каждая из которых считалась несколько минут. И решил я, чтобы ловчее отлаживать тот скрипт, выводить гра...
Всем привет, я работаю программистом-исследователем в команде компьютерного зрения Mail.ru Group. Ко Дню Победы в этом году мы решили сделать проект по реставрации военных фотографий. Что так...