JavaRush /Курси /Модуль 5. Spring /Допоміжні класи фреймворку TestContext

Допоміжні класи фреймворку TestContext

Модуль 5. Spring
Рівень 11 , Лекція 12
Відкрита

Засіб виконання з JUnit 4 у Spring

Spring TestContext Framework пропонує повну інтеграцію з JUnit 4 через спеціальний засіб виконання (runner) (підтримується на JUnit 4.12 або вище). Позначаючи тестові класи анотацією @RunWith(SpringJUnit4ClassRunner.class) або коротшим варіантом @RunWith(SpringRunner.class), розробники можуть реалізувати стандартні модульні та інтеграційні тести на основі JUnit 4 та одночасно використовувати переваги фреймворку TestContext, такі як підтримка завантаження контекстів програми, впровадження залежностей в екземпляри тестів, транзакційне виконання тестових методів тощо. Якщо тобі необхідно використовувати Spring TestContext Framework з альтернативним засобом виконання (таким як засіб виконання Parameterized з JUnit 4) або сторонніми засобами виконання (такими як MockitoJUnitRunner), ти можеш за бажанням використовувати засоби підтримки Spring для правил JUnit замість цього.

У наступному лістингу коду показані мінімальні вимоги, щоб налаштувати тестовий клас для виконання за допомогою спеціального Runner у Spring:

Java

@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
    @Test
    public void testMethod() {
        // логіка тестування...
    }
}
Kotlin

@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
    @Test
    fun testMethod() {
        // логіка тестування...
    }
}

У попередньому прикладі анотація @TestExecutionListeners налаштована з порожнім списком, щоб відключити слухачів за замовчуванням, які в іншому випадку вимагають конфігурування ApplicationContext через анотацію @ContextConfiguration.

Правила Spring JUnit 4

Пакет org.springframework.test.context.junit4.rules надає наступні правила JUnit 4 (підтримуються на JUnit 4.12 або вище):

  • SpringClassRule

  • SpringMethodRule

SpringClassRule — це TestRule з JUnit, що підтримує функції Spring TestContext Framework на рівні класів, а SpringMethodRule — це MethodRule з JUnit, що підтримує функції Spring TestContext Framework на рівні екземплярів та методів.

На відміну від SpringRunner, перевагою засобу підтримки JUnit на основі правил у Spring є те, що воно незалежно від будь-якої реалізації org.junit.runner.Runner і тому може бути поєднано з існуючими альтернативними засобами виконання (такими як Parameterized з JUnit 4) або сторонніми засобами виконання (такими як MockitoJUnitRunner).

Для підтримки повної функціональності фреймворку TestContext необхідно об'єднати SpringClassRule з SpringMethodRule. У наступному прикладі показано правильний спосіб оголошення цих правил в інтеграційному тесті:

Java

// Опціонально вказуємо засіб виконання, що не є Runner з Spring через @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();
    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();
    @Test
    public void testMethod() {
        // логіка тестування...
    }
}
Kotlin

// Опціонально вказуємо засіб виконання, що не є Runner з Spring через @RunWith(...)
@ContextConfiguration
class IntegrationTest {
    @Rule
    val springMethodRule = SpringMethodRule()
    @Test
    fun testMethod() {
        // логіка тестування...
    }
    companion object {
        @ClassRule
        val springClassRule = SpringClassRule()
    }
}

Допоміжні класи JUnit 4

Пакет org.springframework.test.context.junit4 надає наступні допоміжні класи для тестових випадків на базі JUnit 4 (підтримується на JUnit 4.12 або вище):

  • AbstractJUnit4SpringContextTests

  • AbstractTransactional

    AbstractJUnit4SpringContextTests — це абстрактний базовий тестовий клас, який інтегрує Spring TestContext Framework з явною підтримкою тестування ApplicationContext у середу JUnit 4. Якщо ти розшириш AbstractJU , то можеш отримати доступ до protected змінної екземпляра applicationContext, яку можна використовувати для виконання явного пошуку бінів або для тестування стану контексту в цілому.

    AbstractTransactionalJUnit4SpringContextTests – це абстрактне транзакційне розширення AbstractJUnit4SpringContextTests, яке додає деякі зручні функції для доступу до JDBC. Цей клас очікує, що в ApplicationContext буде визначено бін javax.sql.DataSource та бін PlatformTransactionManager. Якщо ти розшириш AbstractTransactionalJUnit4SpringContextTests, то можеш отримати доступ до protected змінної екземпляра jdbcTemplate, яку можна використовувати, щоб виконувати SQL-інструкції для здійснення запитів до бази даних. Можна використовувати такі запити для підтвердження стану бази даних як до, так і після виконання коду програми, пов'язаного з базою даних, а Spring гарантовано забезпечить, що такі запити будуть виконані в рамках тієї ж транзакції, що код програми. При використанні в поєднанні з інструментом ORM слід уникати хибних спрацювань. Як згадувалося в розділі Підтримка тестування JDBC", AbstractTransactionalJUnit4SpringContextTests також надає допоміжні методи, які делегують повноваження методам у JdbcTestUtils за допомогою вищезазначеного jdbc. AbstractTransactionalJUnit4SpringContextTests надає метод executeSqlScript(..) для виконання SQL-скриптів щодо налаштованого DataSource.

    Ці класи є допоміжним засобом для розширення. Якщо вам не потрібно, щоб ваші тестові класи були прив'язані до ієрархії класів, специфічної для Spring, то можеш налаштувати власні спеціальні тестові класи за допомогою анотації @RunWith(SpringRunner.class) або правил JUnit для Spring.

    SpringExtension для JUnit Jupiter

    Spring TestContext Framework пропонує повну інтеграцію з тестовим фреймворком JUnit Jupiter, наявним у JUnit 5. Анотуючи класи тестів за допомогою @ExtendWith(SpringExtension.class), можна реалізувати стандартні модульні та інтеграційні тести на основі JUnit Jupiter та одночасно користуватися перевагами фреймворку TestContext, такими як підтримка завантаження контекстів програми, впровадження залежностей в екземпляри тестів, транзакційне виконання тестових методів і так далі. функцій, що Spring підтримує для JUnit 4 і TestNG:

    • Використання залежностей для тестових конструкторів, тестових методів та методів зворотного виклику життєвого циклу тестів. Див. розділ "Використання залежностей за допомогою SpringExtension" для більш детальної інформації.

    • Повнофункціональна підтримка умовного виконання тестів на основі виразів на мові SpEL, змінних середовища, системних властивостей тощо. Докладнішу інформацію та приклади див. у документації з анотацій @EnabledIf та @DisabledIf у розділі "Анотації з JUnit Jupiter для тестування в Spring".

    • Спеціальні складені анотації, які об'єднують анотації зі Spring і JUnit Jupiter. Докладнішу інформацію див. у прикладах анотацій @TransactionalDevTestConfig та @TransactionalIntegrationTest у розділі "Підтримка мета-анотацій для тестування".

    У наступному лістингу коду показано, як налаштувати тестовий клас для використання SpringExtension у поєднанні з @ContextConfiguration:

    Java
    
    // Даємо команду JUnit Jupiter розширити тест засобами підтримки з Spring.
    @ExtendWith(SpringExtension.class)
    // Даємо команду Spring завантажити ApplicationContext з TestConfig.class
    @ContextConfiguration(classes = TestConfig.class)
    class SimpleTests {
        @Test void testMethod() {
            // логіка тестування...
        }
    } 
    Kotlin
    
    // Даємо команду JUnit Jupiter розширити тест засобами підтримки з Spring.
    @ExtendWith(SpringExtension::class)
    // Даємо команду Spring завантажити ApplicationContext з TestConfig.class
    @ContextConfiguration(classes = [TestConfig::class])
    class SimpleTests {
        @Test
        fun testMethod() {
            // логіка тестування...
        }
    } 

    Оскільки в JUnit 5 можна також використовувати анотації як мета-анотації, Spring надає складові анотації @SpringJUnitConfig та @SpringJUnitWebConfig для спрощення конфігурації тестового ApplicationContext та JUnit Jupiter.

    У наступному прикладі анотація @SpringJUnitConfig використовується для зменшення обсягу конфігурації, використаної в попередній приклад:

    Java
    
    // Дає Spring команду зареєструвати SpringExtension в JUnit
    // Jupiter і завантажити ApplicationContext з TestConfig.class
    @SpringJUnitConfig(TestConfig.class)
    class SimpleTests {
        @Test
        void testMethod() {
            // логіка тестування...
        }
    }
    Kotlin
    
    // Дає Spring команду зареєструвати SpringExtension в JUnit
    // Jupiter і завантажити ApplicationContext з TestConfig.class
    @SpringJUnitConfig(TestConfig::class)
    class SimpleTests {
        @Test
        fun testMethod() {
            // логіка тестування...
        }
    }

    Аналогічно, в наступному прикладі анотація @SpringJUnitWebConfig використана, щоб створити WebApplicationContext для використання з JUnit Jupiter:

    Java
    
    // Дає Spring команду зареєструвати SpringExtension в JUnit
    // Jupiter і завантажити WebApplicationContext з TestWebConfig.class
    @SpringJUnitWebConfig(TestWebConfig.class)
    class SimpleWebTests {
        @Test
        void testMethod() {
            // логіка тестування...
        }
    }
    Kotlin
    
    // Дає Spring команду зареєструвати SpringExtension в JUnit
    // Jupiter і завантажити WebApplicationContext from TestWebConfig::class
    @SpringJUnitWebConfig(TestWebConfig::class)
    class SimpleWebTests {
        @Test
        fun testMethod() {
            // логіка тестування...
        }
    }

    Детальнішу інформацію див. у документації @SpringJUnitConfig та @SpringJUnitWebConfig у розділі "Анотації з JUnit Jupiter для тестування в Spring".

    Використання залежностей за допомогою SpringExtension

    SpringExtension реалізує API-інтерфейс розширення ParameterResolver з JUnit Jupiter, що дозволяє Spring забезпечити впровадження залежностей для тестових конструкторів, тестових методів та методів зворотного виклику життєвого циклу тестів.

    Зокрема, SpringExtension може впроваджувати залежності з ApplicationContext тесту в тестові конструктори та методи, анотовані @BeforeAll, @AfterAll, @BeforeEach, @AfterEach, @Test, @RepeatedTest, @ParameterizedTest та іншими анотаціями.

    Впровадження залежностей через конструктор

    Якщо певний параметр у конструкторі тестового класу з JUnit Jupiter має тип ApplicationContext (або його підтип) або анотований чи мета-анотований за допомогою @Autowired, @Qualifier чи @Value, Spring впроваджує значення цього параметра з використанням відповідного біна або значення з ApplicationContext тесту.

    Spring також можна налаштувати на автоматичне виявлення та зв'язування всіх аргументів конструктора тестового класу, якщо конструктор вважається автоматично зв'язуваним. Конструктор вважається таким, що автоматично зв'язується, якщо виконується одна з наступних умов (у порядку старшинства).

    • Конструктор анотований @Autowired.

    • Анотація @TestConstructor присутня або мета-присутня для тестового класу з атрибутом autowireMode, встановленим у ALL.

    • Режим автоматичного виявлення та зв'язування тестового конструктора за замовчуванням було змінено на ALL.

    Докладніше про використання анотації @TestConstructor та про те, як змінити режим автоматичного виявлення та зв'язування глобального тестового конструктора, див. у розділі, присвяченому анотації @TestConstructor.

    Якщо конструктор для тестового класу вважається автоматично зв'язуваним, Spring бере на себе дозвіл аргументів для всіх параметрів у конструкторі. Отже, жодний інший ParameterResolver, зареєстрований у JUnit Jupiter, не може дозволяти параметри для такого конструктора.

    Впровадження залежностей через конструктор для тестових класів не можна використовувати в поєднанні із засобами підтримки анотації @TestInstance(PER_CLASS) з JUnit Jupiter, якщо анотація @DirtiesContext використовується для закриття ApplicationContext тесту перед або після тестових методів.

    Причина в тому, що анотація @TestInstance(PER_CLASS) дає JUnit Jupiter команду кешувати екземпляр тесту між викликами тестового методу. Отже, тестовий екземпляр зберігатиме посилання на біни, спочатку впроваджені з ApplicationContext, який згодом був закритий. Оскільки конструктор для тестового класу в таких сценаріях буде викликаний лише один раз, впровадження залежностей не повториться, і наступні тести взаємодіятимуть із бінами із закритого ApplicationContext, що може призвести до помилок.

    Щоб використовувати анотацію @DirtiesContext у режимі "перед тестовим методом" або "після тестового методу" у поєднанні з анотацією @TestInstance(PER_CLASS), необхідно налаштувати залежності від Spring на їх отримання шляхом впровадження через поле або сеттер, щоб їх можна було повторно впроваджувати між викликами тестового методу.

    У наступному прикладі Spring впроваджує бін OrderService з ApplicationContext, завантаженого з TestConfig.class, в конструктор OrderServiceIntegrationTests.

    Java
    
    @SpringJUnitConfig(TestConfig .class)
    class OrderServiceIntegrationTests {
        private final OrderService orderService;
        @Autowired OrderServiceIntegrationTests(OrderService orderService) {
            this.orderService = orderService;
        }
        // тести, які використовують впроваджену службу OrderService
    }
    Kotlin
    
    @SpringJUnitConfig(TestConfig::class)
    class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
        // тести, які використовують впроваджену службу OrderService
    }

    Зверни увагу, що ця функція дозволяє тестовим залежностям бути final і, отже, незмінними.

    Якщо властивість spring.test.constructor.autowire.mode має значення all (див. @TestConstructor), можна пропустити оголошення анотації @Autowired для конструктора з попереднього прикладу, в результаті чого отримаємо наступне.

    Java
    
    @SpringJUnitConfig(TestConfig.class)
    class OrderServiceIntegrationTests {
        private final OrderService orderService;
        OrderServiceIntegrationTests(OrderService orderService) {
            this.orderService = orderService;
        }
        // тести, які використовують впроваджену службу OrderService
    }
    Kotlin
    
    @SpringJUnitConfig(TestConfig::class)
    class OrderServiceIntegrationTests(val orderService:OrderService) {
        // тести, які використовують впроваджену службу OrderService
    }
    Впровадження залежностей через метод

    Якщо параметр тестового методу з JUnit Jupiter або методу зворотного виклику життєвого циклу тесту має тип ApplicationContext (чи його підтип) або анотований чи мета-анотований за допомогою анотацій @Autowired, @Qualifier або @Value, Spring впроваджує значення для цього конкретного параметра з використанням відповідного бына з ApplicationContext тесту.

    У наступному прикладі Spring впроваджує OrderService з ApplicationContext, завантаженого з TestConfig.class, до тестового методу deleteOrder():

    Java
    
    @SpringJUnitConfig(TestConfig.class)
    class OrderServiceIntegrationTests {
        @Test
        void deleteOrder(@Autowired OrderService orderService) {
            // використовуємо orderService з ApplicationContext тесту
        }
    }
    Kotlin
    
    @SpringJUnitConfig(TestConfig::class)
    class OrderServiceIntegrationTests {
        @Test
        fun deleteOrder(@Autowired orderService: OrderService) {
            // використовуємо orderService з ApplicationContext тесту
        }
    } 

    Завдяки надійності підтримки ParameterResolver в JUnit Jupiter, до одного методу можна впровадити кілька залежностей, причому не тільки зі Spring, а й із самого JUnit Jupiter або інших сторонніх розширень.

    У наступному прикладі показано, як зробити так, щоб Spring та JUnit Jupiter одночасно впроваджували залежності до тестового методу placeOrderRepeatedly().

    Java
    
    @SpringJUnitConfig(TestConfig.class)
    class OrderServiceIntegrationTests {
        @RepeatedTest(10)
        void placeOrderRepeatedly(RepetitionInfo repeatedInfo,
                @Autowired OrderService orderService) {
            // використовуємо orderService з ApplicationContext тесту
            // та repetitionInfo з JUnit Jupiter
        }
    }
    Kotlin
    
    @SpringJUnitConfig(TestConfig::class)
    class OrderServiceIntegrationTests {
        @RepeatedTest (10)
        fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {
            // використовуємо orderService з ApplicationContext тесту
            // та repetitionInfo з JUnit Jupiter
        }
    }

    Зверни увагу, що використання анотації @RepeatedTest з JUnit Jupiter дозволяє тестовому методу отримувати доступ до RepetitionInfo.

    Конфігурація тестового класу з анотацією @Nested

    Spring TestContext Framework підтримує використання пов'язаних з тестами анотацій для тестових класів, позначених анотацією @Nested, в JUnit Jupiter, починаючи з версії Spring Framework 5.0; однак до Spring Framework 5.3 конфігураційні анотації тестів на рівні класів не успадковувалися від вкладених класів, як це відбувається з суперкласами. У Spring Framework 5.3 з'явилася повнофункціональна підтримка успадкування конфігурації тестового класу від вкладених класів, і така конфігурація успадковуватиметься за замовчуванням. Щоб змінити режим INHERIT за замовчуванням на режим OVERRIDE, можна позначити окремий тестовий клас, анотований @Nested, анотацією @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE). Явне оголошення @NestedTestConfiguration застосовуватиметься до анотованого тестового класу, а також до всіх його підкласів та вкладених класів. Таким чином, можна анотувати високорівневий тестовий клас за допомогою анотації @NestedTestConfiguration, і це буде застосовано до всіх його вкладених тестових класів рекурсивно.

    Для того, щоб команди розробників могли змінити значення за замовчуванням на OVERRIDE — наприклад, для забезпечення сумісності з версіями Spring Framework 5.0-5.2 – режим за замовчуванням можна змінити глобально через системну властивість JVM або файл spring.properties в корені шляху класів. Докладніші відомості див. у статті "Зміна режиму наслідування об'ємної конфігурації за замовчуванням".

    Хоча наступний приклад Hello World дуже спрощений, він показує, як оголосити конфігурацію для високорівневого класу, яка успадковується його тестовими класами, позначеними анотацією @Nested. У цьому прикладі успадковується лише конфігураційний клас TestConfig. Кожен вкладений тестовий клас надає свій власний набір активних профілів, внаслідок чого для кожного вкладеного тестового класу створюється окремий ApplicationContext (докладніше див. розділ "Кешування контексту"). Зверніться до списку підтримуваних анотацій, щоб дізнатися, які анотації можуть бути успадковані в тестових класах з анотацією @Nested.
    Java
    
    @SpringJUnitConfig(TestConfig.class)
    class GreetingServiceTests {
        @Nested
        @ActiveProfiles("lang_en")
        class EnglishGreetings {
                @Test
                void hello(@Autowired GreetingService service) {
                    assertThat(service.greetWorld()).isEqualTo("Hello World");
                }
        }
        @Nested
        @ActiveProfiles("lang_de")
        class GermanGreetings {
            @Test
            void hello(@Autowired GreetingService service) {
                assertThat(service.greetWorld()).isEqualTo("Hallo Welt");
            }
        }
    }
    Kotlin
    
    @SpringJUnitConfig(TestConfig::class)
    class GreetingServiceTests {
        @Nested
        @ActiveProfiles("lang_en")
        inner class EnglishGreetings {
            @Test
            fun hello(@Autowired service:GreetingService) {
                assertThat(service.greetWorld()).isEqualTo("Hello World")
            }
        }
        @Nested
        @ActiveProfiles("lang_de")
        inner class GermanGreetings {
            @Test
            fun hello(@Autowired service:GreetingService) {
                assertThat(service.greetWorld()).isEqualTo("Hallo Welt")
            }
        }
    }

    Допоміжні класи TestNG

    Пакет org.springframework.test.context.testng надає такі допоміжні класи для тестування на основі TestNG:

    • AbstractTestNGSpringContextTests

    • AbstractTransactionalTestNGSpringContextTests

    AbstractTestNGS SpringContextTests — це абстрактний базовий тестовий клас, який інтегрує Spring TestContext Framework з явною підтримкою тестування ApplicationContext до середовища TestNG. Якщо ти розшириш AbstractTestNGSpringContextTests, можеш отримати доступ до protected змінної екземпляра applicationContext, яку можна використовувати для виконання явного пошуку БІНIВ або для тестування стану контексту в цілому.

    AbstractTransactionalTestNGSpringContextTests розширення AbstractTestNGSpringContextTests, яке додає деякі зручні функції для доступу до JDBC. Цей клас очікує, що в ApplicationContext буде визначено бін javax.sql.DataSource та бін PlatformTransactionManager. Якщо ти розшириш AbstractTransactionalTestNGSpringContextTests, можна отримати доступ до protected змінної екземпляра jdbcTemplate, яку можна використовувати, щоб виконувати SQL-інструкції для здійснення запитів до бази даних. Можна використовувати такі запити для підтвердження стану бази даних як до, так і після виконання коду програми, пов'язаного з базою даних, а Spring гарантовано забезпечить, що такі запити будуть виконані в рамках тієї ж транзакції, що код програми. При використанні в поєднанні з інструментом ORM слід уникати хибних спрацювань. Як згадувалося в розділі "Підтримка тестування JDBC", AbstractTransactionalTestNGSpringContextTests також надає допоміжні методи, які делегують повноваження методам JdbcTestUtils за допомогою вищезазначеного jdbc До того ж, AbstractTransactionalTestNGSpringContextTests надає метод executeSqlScript(..) для виконання SQL-скриптів щодо налаштованого DataSource.

    Ці класи є допоміжним засобом для розширення. Якщо тобі не потрібно, щоб тестові класи були прив'язані до ієрархії класів, специфічної для Spring, то можеш налаштувати свої власні спеціальні тестові класи за допомогою анотацій @ContextConfiguration, @TestExecutionListeners тощо, а також вручну інструментувати свій тестовий клас за допомогою TestContextManager. Дивися вихідний код AbstractTestNGSpringContextTests як приклад того, як можна інструментувати твій тестовий клас.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ