При написанні інтеграційних тестів для реляційної бази даних найчастіше корисно виконувати SQL-скрипти для зміни схеми бази даних або вставки тестових даних в таблиці. Модуль spring-jdbc забезпечує підтримку ініціалізації вбудованої або існуючої бази даних шляхом виконання SQL-скриптів під час завантаження ApplicationContext у Spring. Докладніше див. у розділах "Підтримка вбудованих баз даних" та "Тестування логіки доступу до даних з використанням вбудованої бази даних".

Хоч і дуже розумно ініціалізувати базу даних для тестування одноразу під час завантаження ApplicationContext, іноді все ж таки необхідно мати можливість змінювати базу даних під час інтеграційних тестів. У наступних розділах пояснюється, як запускати SQL-скрипти програмно і декларативно під час інтеграційних тестів. тестування.

  • org.springframework.jdbc.datasource.init.ScriptUtils

  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator

  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests

    >
  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils надає колекцію статичних методів для роботи з SQL-скриптами та в основному призначений для внутрішнього використання в рамках фреймворку. Однак, якщо необхідний повний контроль над аналізом та виконанням SQL-скриптів, ScriptUtils може підходити краще, ніж деякі інші альтернативи, описані далі. Детальнішу інформацію дивіться у javadoc за окремими методами в ScriptUtils.

ResourceDatabasePopulator надає об'єктно-орієнтований API-інтерфейс для програмного заповнення, ініціалізації або очищення бази даних за допомогою SQL скриптів, визначених у зовнішніх ресурсах. ResourceDatabasePopulator надає опції для конфігурування кодування символів, роздільника інструкцій, розмежувачів коментарів та прапорів обробки помилок, які використовуються при синтаксичному аналізі та виконанні скриптів. Кожен із параметрів конфігурації має значення за замовчуванням. Докладніше про значення за замовчуванням див. у javadoc. Для виконання скриптів, налаштованих у ResourceDatabasePopulator, можна викликати або метод populate(Connection), щоб виконати модуль заповнення щодо java.sql.Connection, або метод execute(DataSource), щоб виконати модуль заповнення щодо javax.sql.DataSource. У наступному прикладі задані SQL-скрипти для тестової схеми та тестових даних, роздільник інструкцій встановлений у значення @@, а скрипти запускаються щодо DataSource:

Java
@Test void databaseTest() { ResourceDatabasePopulator populator = новий ResourceDatabasePopulator(); populator.addScripts( new ClassPathResource("test-schema.sql"), new ClassPathResource("test-data.sql")); populator.setSeparator("@@"); populator.execute(this.dataSource); // запускаємо код, який використовує тестову схему та дані }
Kotlin
@Test fun databaseTest() { val populator = ResourceDatabasePopulator() populator.addScripts( ClassPathResource("test-schema.sql"), ClassPathResource("test-data.sql")) populator.setSeparator("@@") populator.execute(dataSource) // запускаємо код , що використовує тестову схему та дані }

Зверніть увагу, що ResourceDatabasePopulator внутрішньо делегує ScriptUtils повноваження для синтаксичного аналізу та виконання SQL-скриптів. Так само методи executeSqlScript(..) в AbstractTransactionalJUnit4SpringContextTests і AbstractTransactionalTestNGSpringContextTests внутрішньо використовують ResourceDatabasePopulator . Більш детальну інформацію дивіться в Javadoc за різними методами executeSqlScript(..).

Декларативне виконання SQL-скриптів за допомогою анотації @Sql

На додаток до вищезазначених механізмів для виконання SQL-скриптів програмно, можна декларативно конфігурувати SQL-скрипти у Spring TestContext Framework. Зокрема, можна оголосити анотацію @Sql для тестового класу або тестового методу, щоб налаштувати окремі SQL-інструкції або шляхи до ресурсів SQL-скриптів, які повинні бути виконані щодо заданої бази даних перед або після методу інтеграційного тестування. Підтримка анотації @Sql забезпечується слухачем SqlScriptsTestExecutionListener, який активовано за умовчанням.

Оголошення анотації @Sql на рівні методу за умовчанням перевизначають оголошення на рівні класів. Однак, починаючи зі Spring Framework 5.2, цю логіку роботи можна налаштувати для кожного тестового класу або кожного тестового методу через анотацію @SqlMergeMode.
Семантика ресурсу шляху

Кожен шлях інтерпретується як Resource із Spring. Простий шлях (наприклад, "schema.sql") вважається ресурсом шляху класів, який належить до пакета, в якому визначено тестовий клас. Шлях, що починається з косої межі, вважається абсолютним ресурсом шляху класів (наприклад, "/org/example/schema.sql"). Шлях, що посилається на URL-адресу (наприклад, шлях з префіксами classpath:, file:, http:), завантажується за допомогою зазначеного протоколу ресурсу .

У наступному прикладі показано, як використовувати інструкцію @Sql на рівні класу та на рівні методу в класі інтеграційного тесту на основі JUnit Jupiter:

Java
@SpringJUnitConfig @Sql("/test-schema.sql") class DatabaseTests { @Test void emptySchemaTest() { // виконуємо код, який використовує тестову схему без будь-яких тестових даних } @ Test @Sql({"/test-schema.sql", "/test-user-data.sql"}) void userTest() { // виконуємо код, який використовує тестову схему та тестові дані } }
Kotlin
@SpringJUnitConfig @Sql("/test-schema.sql") class DatabaseTests { @Test fun emptySchemaTest() { / / виконуємо код, який використовує тестову схему без будь-яких тестових даних } @Test @Sql("/test-schema.sql", "/test-user-data.sql") fun userTest() { // виконуємо код, який використовує тестову схему та тестові дані } }
Виявлення стандартних скриптів

Якщо скрипти або інструкції SQL не задані, робиться спроба визначити default скрипт залежно від того, де оголошено інструкцію @Sql. Якщо значення за промовчанням не може бути визначено, буде згенеровано виняток IllegalStateException.

  • Оголошення на рівні класу: Якщо анотований тестовий клас - com.example .MyTest, то відповідний скрипт за промовчанням - classpath:com/example/MyTest.sql.

  • Оголошення на рівні методу: Якщо анотований тестовий метод має ім'я testMethod() і визначено у класі com.example.MyTest, то відповідний скрипт за умовчанням буде називатися classpath:com/example/MyTest. testMethod.sql.

Оголошення кількох наборів анотацій @Sql

Якщо потрібно налаштувати кілька наборів SQL-скриптів для даного класу або методу тестування, але з різною конфігурацією синтаксису, різними правилами обробки помилок або різними фазами виконання кожного набору, можна оголосити кілька екземплярів анотації @Sql. У Java 8 можна використовувати інструкцію @Sql як повторювану інструкцію. Або ж можна використовувати анотацію @SqlGroup як явний контейнер для оголошення кількох екземплярів анотації @Sql.

У наступному прикладі показано, як використовувати анотацію @Sql як повторювана інструкція в Java 8:

Java
@Test @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")) @Sql("/test-user-data.sql") void userTest() { // виконуємо код, який використовує тестову схему та тестові дані }
Kotlin
// Повторювані анотації з не-SOURCE збереженням поки що не підтримуються Kotlin

У сценарії, представленому в попередньому прикладі, скрипт test-schema.sql використовує інший синтаксис для однорядкових коментарів.

Наступний приклад ідентичний попередньому, за винятком того, що оголошення анотації @Sql згруповані разом в анотації @SqlGroup. У версії Java 8 і вище використання анотації @SqlGroup необов'язкове, але анотацію @SqlGroup може знадобитися використовувати для сумісності з іншими мовами JVM, такими як Kotlin.

Java
@Test @SqlGroup({ @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), @Sql(" /test-user-data.sql") )} void userTest() { // виконуємо код, який використовує тестову схему та тестові дані }
Kotlin
@Test @SqlGroup( Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`"))), Sql("/test-user-data.sql" )) fun userTest() { // виконуємо код, який використовує тестову схему та тестові дані }
Фази виконання скриптів

За умовчанням SQL -Скрипти виконуються перед відповідним тестовим методом Однак, якщо потрібно запустити певний набір скриптів після тестового методу (наприклад, для очищення стану бази даних), можна використовувати атрибут executionPhase в анотації @Sql, як показано в наступному прикладі:

Java
@Test @Sql(scripts = "create-test-data.sql", config = @SqlConfig(transactionMode = ISOLATED) ) @Sql( scripts = "delete-test-data.sql", config = @SqlConfig(transactionMode = ISOLATED), executionPhase = AFTER_TEST_METHOD ) void userTest() { // виконуємо код, який наказує фіксувати тестові дані // у базі даних поза тестовою транзакцією }
Kotlin
@Test @SqlGroup( Sql("create-test-data.sql", config = SqlConfig( transactionMode = ISOLATED)), Sql("delete-test-data.sql", config = SqlConfig(transactionMode = ISOLATED), executionPhase = AFTER_TEST_METHOD)) fun userTest() { // виконуємо код, який наказує фіксувати тестові дані // в базі даних поза тестовою транзакцією }

Зверніть увагу, що ISOLATED та AFTER_TEST_METHOD статично імпортовані з < code>Sql.TransactionMode та Sql.ExecutionPhase, відповідно.

Конфігурування скрипту за допомогою @SqlConfig

Можна конфігурувати синтаксичний аналіз скрипту та обробку помилок за допомогою анотації @SqlConfig. Якщо @SqlConfig оголошується як інструкція на рівні класу для класу інтеграційного тесту, то вона є глобальною конфігурацією для всіх SQL-скриптів в ієрархії тестового класу. При прямому оголошенні за допомогою атрибуту config анотації @Sql, анотація @SqlConfig служить локальною конфігурацією для SQL-скриптів, оголошених в інструкції @ Sql. Кожен атрибут в інструкції @SqlConfig має неявне значення за промовчанням, яке документовано в javadoc відповідного атрибуту. Через правила, визначені для атрибутів анотацій у специфікації мови Java (Java Language Specification), на жаль, неможливо привласнити атрибуту анотації значення null. Таким чином, для підтримки перевизначення успадкованої глобальної конфігурації атрибути інструкції @SqlConfig мають явне значення за умовчанням або "" (для рядків), {} ( для масивів), або DEFAULT (для перерахувань). Цей підхід дозволяє локальним оголошенням анотації @SqlConfig вибірково перевизначати окремі атрибути з глобальних оголошень анотації @SqlConfig, надаючи значення, відмінне від "", {} або DEFAULT. Глобальні атрибути анотації @SqlConfig успадковуються завжди, якщо локальні атрибути анотації @SqlConfig не надають явного значення, відмінного від "", {} або DEFAULT. Тому явна локальна конфігурація має пріоритет над глобальною конфігурацією.

Параметри конфігурації, що надаються анотаціями @Sql та @SqlConfig, еквівалентні тим, що підтримуються ScriptUtils та ResourceDatabasePopulator, але є надмножиною тих, які надаються елементом простору імен XML <jdbc:initialize-database/>. Подробиці дивіться в javadoc по окремих атрибутах в анотаціях @Sql та @SqlConfig.

Керування транзакціями для анотації @Sql

За умовчанням SqlScriptsTestExecutionListener створює бажану семантику транзакцій для скриптів, налаштованих за допомогою анотації @Sql. Зокрема, SQL-скрипти виконуються без транзакції в рамках існуючої транзакції, керованої Spring (наприклад, транзакції, керованої TransactionalTestExecutionListener для тесту, позначеного анотацією @Transactional) або в рамках ізольованої транзакції , залежно від конфігурованого значення атрибуту transactionMode в анотації @SqlConfig та наявності PlatformTransactionManager у ApplicationContext тесту. Однак, як мінімум, javax.sql.DataSource має бути присутнім у ApplicationContext тесту.

Якщо алгоритми, що використовуються SqlScriptsTestExecutionListener для визначення DataSource та PlatformTransactionManager та виведення семантики транзакції не задовольняють вашим потребам, то можна вказати явні імена, встановивши атрибути dataSource та transactionManager в інструкції @SqlConfig. Крім того, можна керувати логікою поширення транзакцій, задаючи атрибут transactionMode в анотації @SqlConfig (наприклад, чи слід запускати скрипти в ізольованій транзакції). Хоча докладний розгляд усіх підтримуваних варіантів керування транзакціями за допомогою анотації @Sql виходить за рамки цього довідкового посібника, javadoc для анотації @SqlConfig та слухача SqlScriptsTestExecution містять детальну інформацію, а в наступному прикладі показаний типовий сценарій тестування з використанням JUnit Jupiter та транзакційних тестів з анотацією @Sql:

Java
@SpringJUnitConfig(TestDatabaseConfig.class) @Transactional class TransactionalSqlScriptsTests { final JdbcTemplate jdbcTemplate; @Autowired TransactionalSqlScriptsTests(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } @Test @Sql("/test-data.sql") void usersTest() { // перевіряємо стан у тестовій базі даних: assertNumUsers(2); // виконуємо код, який використовує тестові дані... } int countRowsInTable(String tableName) { return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); } void assertNumUsers(int expected) { assertEquals(expected, countRowsInTable("user"), "Number of rows in the [user] table."); } }
Kotlin
@SpringJUnitConfig(TestDatabaseConfig::class) @Transactional class TransactionalSqlScriptsTests @Autowired constructor(dataSource : DataSource) { val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource) @Test @Sql("/test-data.sql") fun usersTest() { // перевіряємо стан у тестовій базі даних: assertNumUsers(2) // виконуємо код, використовує тестові дані... } fun countRowsInTable(tableName: String): Int { return in the [user] table.") } }

Зверніть увагу, що немає необхідності очищати базу даних після виконання методу usersTest(), оскільки будь-які зміни, внесені до бази даних (як у тестовому методі, так і в скрипті /test-data.sql ), автоматично відкочуються TransactionalTestExecutionListener (див. докладніше у розділі, присвяченому управлінню транзакціями ). executing-sql-declaratively-script-merging">Об'єднання та перевизначення конфігурації за допомогою анотації @SqlMergeMode

Починаючи зі Spring Framework 5.2, можна об'єднувати оголошення анотації @Sql на рівні методів з оголошеннями на рівні класів. Наприклад, це дозволить надавати конфігурацію для схеми бази даних або деякі загальні тестові дані для тестового класу один раз, а потім надавати додаткові, специфічні для конкретного випадку тестові дані для кожного тестового методу. Щоб активувати об'єднання анотацій @Sql, анотуйте ваш тестовий клас або тестовий метод за допомогою анотації @SqlMergeMode(MERGE). Щоб дезактивувати об'єднання для конкретного тестового методу (або конкретного тестового підкласу), можна перейти назад у режим за замовчуванням за допомогою анотації @SqlMergeMode(OVERRIDE).Зверніться до розділу документації, присвяченого анотації @SqlMergeMode для ознайомлення з прикладами та отримання більш детальної інформації.