SQL スクリプトの実行
リレーショナルデータベースに対して統合テストを作成する場合、SQL スクリプトを実行してデータベーススキーマを変更したり、テストデータをテーブルに挿入したりすることが多くの場合有益です。spring-jdbc
モジュールは、Spring ApplicationContext
がロードされたときに SQL スクリプトを実行することにより、組み込みまたは既存のデータベースを初期化するためのサポートを提供します。詳細については、組み込みデータベースのサポートおよび組み込みデータベースを使用したデータアクセスロジックのテストを参照してください。
それは ApplicationContext
がロードされたときに一度テストするためのデータベースを初期化するために非常に有用であるが、時には統合テスト中にデータベースを変更することができることが不可欠です。次のセクションでは、統合テスト中に SQL スクリプトをプログラムおよび実行により実行する方法について説明します。
プログラムによる SQL スクリプトの実行
Spring には、統合テストメソッド内でプログラムによって 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
は、後で説明する他の代替手段よりもニーズに適している場合があります。詳細については、ScriptUtils
の個々のメソッドの javadoc を参照してください。
ResourceDatabasePopulator
は、外部リソースで定義された SQL スクリプトを使用して、プログラムでデータベースにデータを入力、初期化、クリーンアップするためのオブジェクトベースの API を提供します。ResourceDatabasePopulator
には、スクリプトの解析および実行時に使用される文字エンコード、ステートメント区切り文字、コメント区切り文字、エラー処理フラグを構成するためのオプションがあります。各構成オプションには、妥当なデフォルト値があります。デフォルト値の詳細については、javadoc を参照してください。ResourceDatabasePopulator
で構成されたスクリプトを実行するには、populate(Connection)
メソッドを呼び出して、java.sql.Connection
に対してポピュレーターを実行するか、execute(DataSource)
メソッドを呼び出して、javax.sql.DataSource
に対してポピュレーターを実行します。次の例では、テストスキーマとテストデータの SQL スクリプトを指定し、ステートメントセパレーターを @@
に設定し、スクリプトを DataSource
に対して実行します。
Java
Kotlin
@Test
void databaseTest() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(
new ClassPathResource("test-schema.sql"),
new ClassPathResource("test-data.sql"));
populator.setSeparator("@@");
populator.execute(this.dataSource);
// run code that uses the test schema and data
}
@Test
fun databaseTest() {
val populator = ResourceDatabasePopulator()
populator.addScripts(
ClassPathResource("test-schema.sql"),
ClassPathResource("test-data.sql"))
populator.setSeparator("@@")
populator.execute(dataSource)
// run code that uses the test schema and data
}
ResourceDatabasePopulator
は、SQL スクリプトの解析と実行のために内部で ScriptUtils
に委譲することに注意してください。同様に、AbstractTransactionalJUnit4SpringContextTests
および AbstractTransactionalTestNGSpringContextTests
の executeSqlScript(..)
メソッドは、内部で ResourceDatabasePopulator
を使用して SQL スクリプトを実行します。詳細については、さまざまな executeSqlScript(..)
メソッドの Javadoc を参照してください。
@Sql を使用して SQL スクリプトを宣言的に実行する
SQL スクリプトをプログラムで実行するための前述のメカニズムに加えて、Spring TestContext フレームワークで SQL スクリプトを宣言的に構成できます。具体的には、テストクラスまたはテストメソッドで @Sql
アノテーションを宣言して、統合テストクラスまたはテストメソッドの前後に特定のデータベースに対して実行する必要がある個々の SQL ステートメントまたは SQL スクリプトへのリソースパスを構成できます。@Sql
のサポートは SqlScriptsTestExecutionListener
によって提供され、デフォルトで有効になっています。
メソッドレベルの ただし、これは、 |
パスリソースセマンティクス
各パスは Spring Resource
として解釈されます。プレーンパス("schema.sql"
など)は、テストクラスが定義されているパッケージに関連するクラスパスリソースとして扱われます。スラッシュで始まるパスは、絶対クラスパスリソースとして扱われます(たとえば、"/org/example/schema.sql"
)。URL を参照するパス(たとえば、接頭辞 classpath:
、file:
、http:
のパス)は、指定されたリソースプロトコルを使用してロードされます。
次の例は、JUnit Jupiter ベースの統合テストクラス内で、クラスレベルおよびメソッドレベルで @Sql
を使用する方法を示しています。
Java
Kotlin
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// run code that uses the test schema and test data
}
}
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
fun emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-schema.sql", "/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
}
デフォルトのスクリプト検出
SQL スクリプトまたはステートメントが指定されていない場合、@Sql
が宣言されている場所に応じて、default
スクリプトの検出が試行されます。デフォルトを検出できない場合、IllegalStateException
がスローされます。
クラスレベルの宣言: アノテーション付きテストクラスが
com.example.MyTest
の場合、対応するデフォルトスクリプトはclasspath:com/example/MyTest.sql
です。メソッドレベルの宣言: アノテーション付きテストメソッドの名前が
testMethod()
で、クラスcom.example.MyTest
で定義されている場合、対応するデフォルトスクリプトはclasspath:com/example/MyTest.testMethod.sql
です。
SQL スクリプトとステートメントのログ記録
どの SQL スクリプトが実行されているかを確認したい場合は、org.springframework.test.context.jdbc
ログカテゴリを DEBUG
に設定します。
どの SQL ステートメントが実行されているかを確認したい場合は、org.springframework.jdbc.datasource.init
ログカテゴリを DEBUG
に設定します。
複数の @Sql
セットの宣言
特定のテストクラスまたはテストメソッドに対して SQL スクリプトの複数のセットを構成する必要があるが、セットごとに異なる構文構成、異なるエラー処理ルール、異なる実行フェーズを使用する必要がある場合は、@Sql
の複数のインスタンスを宣言できます。@Sql
を反復可能なアノテーションとして使用することも、@SqlGroup
アノテーションを @Sql
の複数のインスタンスを宣言するための明示的なコンテナーとして使用することもできます。
次の例は、@Sql
を反復可能なアノテーションとして使用する方法を示しています。
Java
Kotlin
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
@Test
@Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
前の例で示したシナリオでは、test-schema.sql
スクリプトは単一行コメントに異なる構文を使用します。
次の例は、@Sql
宣言が @SqlGroup
内でグループ化されていることを除いて、前の例と同じです。@SqlGroup
の使用はオプションですが、他の JVM 言語との互換性のために @SqlGroup
の使用が必要になる場合があります。
Java
Kotlin
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
@Test
@SqlGroup(
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
Sql("/test-user-data.sql")
)
fun userTest() {
// Run code that uses the test schema and test data
}
スクリプト実行フェーズ
デフォルトでは、SQL スクリプトは対応するテストメソッドの前に実行されます。ただし、テストメソッドの後に特定のスクリプトセットを実行する必要がある場合 (たとえば、データベース状態をクリーンアップするため)、次の例に示すように、@Sql
の executionPhase
属性を AFTER_TEST_METHOD
に設定できます。
Java
Kotlin
@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() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
@Test
@Sql("create-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED))
@Sql("delete-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD)
fun userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
ISOLATED と AFTER_TEST_METHOD は、それぞれ Sql.TransactionMode と Sql.ExecutionPhase から静的にインポートされます。 |
Spring Framework 6.1 以降では、次の例に示すように、クラスレベルの @Sql
宣言の executionPhase
属性を BEFORE_TEST_CLASS
または AFTER_TEST_CLASS
に設定することで、テストクラスの前後に特定のスクリプトセットを実行できます。
Java
Kotlin
@SpringJUnitConfig
@Sql(scripts = "/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
}
@SpringJUnitConfig
@Sql("/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
class DatabaseTests {
@Test
fun emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
}
BEFORE_TEST_CLASS は Sql.ExecutionPhase から静的にインポートされます。 |
@SqlConfig
を使用したスクリプト構成
@SqlConfig
アノテーションを使用して、スクリプトの解析とエラー処理を構成できます。統合テストクラスでクラスレベルのアノテーションとして宣言されている場合、@SqlConfig
は、テストクラス階層内のすべての SQL スクリプトのグローバル構成として機能します。@Sql
アノテーションの config
属性を使用して直接宣言された場合、@SqlConfig
は、囲んでいる @Sql
アノテーション内で宣言された SQL スクリプトのローカル構成として機能します。@SqlConfig
のすべての属性には暗黙のデフォルト値があり、対応する属性の javadoc に記載されています。Java 言語仕様でアノテーション属性に定義されている規則により、残念ながら、null
の値をアノテーション属性に割り当てることはできません。継承されたグローバル構成のオーバーライドをサポートするために、@SqlConfig
属性には、""
(文字列の場合)、{}
(配列の場合)、DEFAULT
(列挙の場合)のいずれかの明示的なデフォルト値があります。このアプローチにより、@SqlConfig
のローカル宣言は、""
、{}
、DEFAULT
以外の値を提供することにより、@SqlConfig
のグローバル宣言から個々の属性を選択的にオーバーライドできます。グローバル @SqlConfig
属性は、ローカル @SqlConfig
属性が ""
、{}
、DEFAULT
以外の明示的な値を提供しない場合は常に継承されます。明示的なローカル構成はグローバル構成をオーバーライドします。
@Sql
および @SqlConfig
によって提供される構成オプションは、ScriptUtils
および ResourceDatabasePopulator
によってサポートされるものと同等ですが、<jdbc:initialize-database/>
XML 名前空間要素によって提供される構成オプションのスーパーセットです。詳細については、@Sql
(Javadoc) および @SqlConfig
(Javadoc) の個々の属性の javadoc を参照してください。
@Sql
のトランザクション管理
デフォルトでは、SqlScriptsTestExecutionListener
は @Sql
を使用して構成されたスクリプトに必要なトランザクションセマンティクスを推測します。具体的には、SQL スクリプトは、transactionMode
属性の設定値に応じて、トランザクションなしで、既存の Spring 管理トランザクション(たとえば、@Transactional
アノテーションが付けられたテスト用の TransactionalTestExecutionListener
によって管理されるトランザクション)、または分離されたトランザクション内で実行されます。@SqlConfig
およびテストの ApplicationContext
の PlatformTransactionManager
の存在。ただし、最低限、テストの ApplicationContext
に javax.sql.DataSource
が存在する必要があります。
SqlScriptsTestExecutionListener
が DataSource
および PlatformTransactionManager
を検出してトランザクションセマンティクスを推測するために使用するアルゴリズムがニーズに合わない場合は、@SqlConfig
の dataSource
および transactionManager
属性を設定して明示的な名前を指定できます。さらに、@SqlConfig
(たとえば、スクリプトを分離されたトランザクションで実行するかどうか) の transactionMode
属性を設定することにより、トランザクション伝播の動作を制御できます。@Sql
でサポートされているすべてのトランザクション管理オプションについての詳細な説明は、このリファレンスマニュアルの範囲外ですが、@SqlConfig
(Javadoc) と SqlScriptsTestExecutionListener
(Javadoc) の javadoc には詳細な情報が記載されています。次の例は、JUnit Jupiter と @Sql
によるトランザクションテストを使用した一般的なテストシナリオを示しています。
Java
Kotlin
@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() {
// verify state in test database:
assertNumUsers(2);
// run code that uses the test data...
}
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.");
}
}
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {
val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)
@Test
@Sql("/test-data.sql")
fun usersTest() {
// verify state in test database:
assertNumUsers(2)
// run code that uses the test data...
}
fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
fun assertNumUsers(expected: Int) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.")
}
}
データベースに加えられた変更 (テストメソッド内または /test-data.sql
スクリプト内) はすべて TransactionalTestExecutionListener
によって自動的にロールバックされるため、usersTest()
メソッドの実行後にデータベースをクリーンアップする必要がないことに注意してください (詳細についてはトランザクション管理を参照))。
@SqlMergeMode
を使用した構成のマージとオーバーライド
Spring Framework 5.2 以降、メソッドレベルの @Sql
宣言をクラスレベルの宣言とマージすることが可能です。例: これにより、データベーススキーマまたはいくつかの一般的なテストデータの構成をテストクラスごとに 1 回提供し、テストメソッドごとに追加のユースケース固有のテストデータを提供できます。@Sql
マージを有効にするには、テストクラスまたはテストメソッドに @SqlMergeMode(MERGE)
でアノテーションを付けます。特定のテストメソッド(または特定のテストサブクラス)のマージを無効にするには、@SqlMergeMode(OVERRIDE)
を使用してデフォルトモードに戻すことができます。例と詳細については、@SqlMergeMode
アノテーションドキュメントセクションを参照してください。