在针对关系数据库编写集成测试时,通常有利于 运行 SQL 脚本以修改数据库架构或将测试数据插入到表中。该模块支持初始化嵌入式数据库或现有数据库 通过在加载 Spring 时执行 SQL 脚本。有关详细信息,请参阅嵌入式数据库支持使用嵌入式数据库测试数据访问逻辑spring-jdbcApplicationContext

尽管在加载时初始化数据库进行一次测试非常有用,但有时必须能够修改 集成测试期间的数据库。以下各节介绍如何运行 SQL 在集成测试期间以编程和声明方式编写脚本。ApplicationContext

以编程方式执行 SQL 脚本

Spring 提供了以下选项,用于在 集成测试方法。

  • 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 脚本的解析和运行方式,可能适合 您的需求比后面描述的其他一些替代方案更好。请参阅个人的 javadoc 方法。ScriptUtilsScriptUtils

ResourceDatabasePopulator提供基于对象的 API,用于以编程方式填充、 使用外部定义的 SQL 脚本初始化或清理数据库 资源。 提供用于配置字符的选项 编码、语句分隔符、注释分隔符和错误处理标志 解析和运行脚本。每个配置选项都有一个合理的 默认值。请参阅 javadoc 有关默认值的详细信息。要运行在 中配置的脚本,可以调用以下任一方法 针对 或 方法运行填充器 对 .以下示例 指定测试架构和测试数据的 SQL 脚本,将语句分隔符设置为 ,并针对 运行脚本:ResourceDatabasePopulatorResourceDatabasePopulatorpopulate(Connection)java.sql.Connectionexecute(DataSource)javax.sql.DataSource@@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
}

请注意,内部委托 to 进行解析 并运行 SQL 脚本。同样,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests 中的方法在内部使用 a 来运行 SQL 脚本。请参阅 Javadoc 了解 各种方法了解更多详细信息。ResourceDatabasePopulatorScriptUtilsexecuteSqlScript(..)ResourceDatabasePopulatorexecuteSqlScript(..)

使用 @Sql 以声明方式执行 SQL 脚本

除了上述以编程方式运行 SQL 脚本的机制外, 您可以在 Spring TestContext 框架中以声明方式配置 SQL 脚本。 具体来说,您可以将测试类或测试方法上的注释声明为 配置应 在集成测试类或测试方法之前或之后对给定数据库运行。 支持由 提供 ,它已启用 默认情况下。@Sql@SqlSqlScriptsTestExecutionListener

默认情况下,方法级声明会覆盖类级声明,但 行为可以通过 按测试类或按测试方法配置。有关详细信息,请参阅使用 @SqlMergeMode 合并和覆盖配置@Sql@SqlMergeMode

但是,这不适用于为 or 执行阶段配置的类级声明。此类声明不能是 重写,对应的脚本和语句将每个类执行一次 除了任何方法级脚本和语句。BEFORE_TEST_CLASSAFTER_TEST_CLASS

路径资源语义

每条路径都解释为一个 Spring。普通路径(例如,)被视为相对于包的类路径资源 定义了测试类。以斜杠开头的路径被视为绝对路径 类路径资源(例如,)。引用 URL(例如,以 、 、 为前缀的路径)通过使用 指定的资源协议。Resource"schema.sql""/org/example/schema.sql"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 脚本或语句,则会尝试检测脚本,具体取决于声明的位置。如果无法检测到默认值,则会抛出 an。default@SqlIllegalStateException

  • 类级声明:如果带注释的测试类是 , 对应的默认脚本为 。com.example.MyTestclasspath:com/example/MyTest.sql

  • 方法级声明:如果带注释的测试方法已命名且为 在类中定义,对应的默认脚本为。testMethod()com.example.MyTestclasspath:com/example/MyTest.testMethod.sql

记录 SQL 脚本和语句

如果要查看正在执行的 SQL 脚本,请将日志记录类别设置为 。org.springframework.test.context.jdbcDEBUG

如果要查看正在执行的 SQL 语句,请将日志记录类别设置为 。org.springframework.jdbc.datasource.initDEBUG

声明多个集合@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

下面的示例与前面的示例相同,只是声明在 中组合在一起。使用是可选的, 但您可能需要使用以与其他 JVM 语言兼容。@Sql@SqlGroup@SqlGroup@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 脚本在相应的测试方法之前运行。但是,如果您 需要在测试方法之后运行一组特定的脚本(例如,清理 数据库状态),可以将属性设置为 ,如以下示例所示:executionPhase@SqlAFTER_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_METHODSql.TransactionModeSql.ExecutionPhase

从 Spring Framework 6.1 开始,可以在 通过将类级声明中的属性设置为 或,在测试类之后,如以下示例所示:executionPhase@SqlBEFORE_TEST_CLASSAFTER_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

您可以使用注释配置脚本分析和错误处理。 当声明为集成测试类的类级注释时,用作测试类层次结构中所有 SQL 脚本的全局配置。什么时候 使用注释的属性直接声明,用作在封闭注释中声明的 SQL 脚本的本地配置。中的每个属性都有一个隐式默认值,即 记录在相应属性的 javadoc 中。由于定义的规则 Java语言规范中的注解属性,不幸的是,它不是 可以将值赋给注释属性。因此,为了 支持覆盖继承的全局配置,属性具有 显式默认值 (for Strings)、(for arrays) 或 (for 枚举)。此方法允许有选择地覆盖 的本地声明 通过提供值 other 的全局声明中的单个属性 比 、 或 。每当 局部属性不提供除 、 或 之外的显式值。因此,显式本地配置会覆盖全局配置。@SqlConfig@SqlConfigconfig@Sql@SqlConfig@Sql@SqlConfignull@SqlConfig""{}DEFAULT@SqlConfig@SqlConfig""{}DEFAULT@SqlConfig@SqlConfig""{}DEFAULT

提供的配置选项与这些选项等效 受 和 但 是 这些 的 超集 由 XML 命名空间元素提供。请参阅 javadoc 的 @Sql中的各个属性,@SqlConfig了解详细信息。@Sql@SqlConfigScriptUtilsResourceDatabasePopulator<jdbc:initialize-database/>

事务管理@Sql

默认情况下,推断所需的事务 使用 配置的脚本的语义。具体来说,运行 SQL 脚本 没有事务,在现有的 Spring 管理事务中(例如,一个 事务由 for a test annoted with ) 管理,或在独立事务中管理,具体取决于配置的值 的属性和 a 在测试中的存在。作为最低限度, 但是,A 必须存在于测试的 .SqlScriptsTestExecutionListener@SqlTransactionalTestExecutionListener@TransactionaltransactionMode@SqlConfigPlatformTransactionManagerApplicationContextjavax.sql.DataSourceApplicationContext

如果用于检测和推断事务语义的算法不符合您的需求, 可以通过设置 的 和 属性来指定显式名称。此外,您可以控制事务传播 行为,通过设置属性(例如,是否 脚本应在隔离事务中运行)。虽然对所有进行了彻底的讨论 事务管理支持的选项超出了此范围 参考手册中,javadoc for @SqlConfigSqlScriptsTestExecutionListener 提供了详细信息,以下示例显示了一个典型的测试方案 使用 JUnit Jupiter 和事务性测试:SqlScriptsTestExecutionListenerDataSourcePlatformTransactionManagerdataSourcetransactionManager@SqlConfigtransactionMode@SqlConfig@Sql@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.")
	}
}

请注意,该方法 运行,因为对数据库所做的任何更改(无论是在测试方法中还是在脚本中)都会自动回滚(请参阅事务管理 详细信息)。usersTest()/test-data.sqlTransactionalTestExecutionListener

合并和覆盖配置@SqlMergeMode

从 Spring Framework 5.2 开始,可以将方法级声明与 类级声明。例如,这允许您提供 数据库模式或一些常见的测试数据,每个测试类一次,然后提供额外的、 每种测试方法的用例特定测试数据。要启用合并,请对任一内容进行批注 带有 的测试类或测试方法。禁用合并 特定的测试方法(或特定的测试子类),可以切换回默认模式 通过。有关示例和更多详细信息,请参阅@SqlMergeMode注释文档部分@Sql@Sql@SqlMergeMode(MERGE)@SqlMergeMode(OVERRIDE)