- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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

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

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

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

Для этого внесем изменения в 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.

Аннотация

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

@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 [3] можно в официальной документации.

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

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

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

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

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

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

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

@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

Итак...

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

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

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

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

Создадим annotation [5] для хранения префиксов — 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")
                );

    }

}

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

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

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

@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 [8] за совет [9]
2) BoyRobot777 [10] за совет [11]

PS

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

Автор: Вячеслав

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/328062

Ссылки в тексте:

[1] JUnit 5: https://junit.org/junit5/docs/current/user-guide/#overview

[2] аннотацию: https://github.com/bvn13/JavaLessons/blob/master/springboot2-junit5-skiptest/src/test/java/com/bvn13/example/springboot/junit/skiptest/TestEnabled.java

[3] условное выполнение тестов JUnit5: https://junit.org/junit5/docs/current/user-guide/#extensions-conditions

[4] полный код класса TestEnabledCondition: https://github.com/bvn13/JavaLessons/blob/9a34719dbc7b616f0234e4dcd0d5376905aacc2e/springboot2-junit5-skiptest/src/test/java/com/bvn13/example/springboot/junit/skiptest/TestEnabledCondition.java

[5] annotation: https://github.com/bvn13/JavaLessons/blob/master/springboot2-junit5-skiptest/src/test/java/com/bvn13/example/springboot/junit/skiptest/TestEnabledPrefix.java

[6] Полный код класса: https://github.com/bvn13/JavaLessons/blob/master/springboot2-junit5-skiptest/src/test/java/com/bvn13/example/springboot/junit/skiptest/TestEnabledCondition.java

[7] тест-классу: https://github.com/bvn13/JavaLessons/blob/master/springboot2-junit5-skiptest/src/test/java/com/bvn13/example/springboot/junit/skiptest/SkiptestApplicationTests.java

[8] dpash: https://www.reddit.com/user/dpash/

[9] совет: https://www.reddit.com/r/java/comments/cuiqxf/skip_junit_test_according_to_java_springframework/exxz1yr?utm_source=share&utm_medium=web2x

[10] BoyRobot777: https://www.reddit.com/user/BoyRobot777/

[11] совет: https://www.reddit.com/r/java/comments/cuiqxf/skip_junit_test_according_to_java_springframework/exy8cnh?utm_source=share&utm_medium=web2x

[12] README.md: https://github.com/bvn13/JavaLessons/blob/master/springboot2-junit5-skiptest/README.md

[13] Источник: https://habr.com/ru/post/464881/?utm_source=habrahabr&utm_medium=rss&utm_campaign=464881