3.5.7 测试Request和Session作用域bean
Spring从早期开始就支持Request
范围和Session
范围的Bean,你可以按照以下步骤测试Request
范围和Session
范围的Bean:
- 通过使用
@WebAppConfiguration
注解测试类,确保为测试加载WebApplicationContext
。 - 将模拟
Request
或Session
注入到测试实例中,并根据需要准备测试装置。 - 调用从配置的
WebApplicationContext
中检索到的Web组件(具有依赖项注入)。 - 对模拟执行断言。
下一个代码片段显示了登录用例的XML配置。注意,userService
bean与请求范围的loginAction
bean有依赖关系。另外,通过使用SpEL
表达式实例化LoginAction
,该表达式从当前HTTP请求中检索用户名和密码。在我们的测试中,我们想通过TestContext
框架管理的模拟来配置这些请求参数。以下清单显示了此用例的配置:
Request作用域bean配置
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:loginAction-ref="loginAction"/>
<bean id="loginAction" class="com.example.LoginAction"
c:username="#{request.getParameter('user')}"
c:password="#{request.getParameter('pswd')}"
scope="request">
<aop:scoped-proxy/>
</bean>
</beans>
在RequestScopedBeanTests
中,我们将UserService
(即被测对象)和MockHttpServletRequest
都注入到我们的测试实例中。在requestScope()
测试方法中,我们通过在提供的MockHttpServletRequest
中设置请求参数来设置测试配置。当在我们的userService
上调用loginUser()
方法时,可以确保该用户服务可以访问当前MockHttpServletRequest
(即我们刚刚设置参数的那个)在请求范围内的loginAction
。然后,我们可以根据用户名和密码的已知输入对结果进行断言。以下清单显示了如何执行此操作:
@SpringJUnitWebConfig
class RequestScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpServletRequest request;
@Test
void requestScope() {
request.setParameter("user", "enigma");
request.setParameter("pswd", "$pr!ng");
LoginResults results = userService.loginUser();
// assert results
}
}
以下代码段类似于我们之前针对请求范围的Bean看到的代码段。但是,这一次,userService
bean与会话范围的userPreferences
bean有依赖关系。注意,通过使用SpEL表达式实例化UserPreferences
bean,该SpEL表达式从当前HTTP会话中检索主题。在我们的测试中,我们需要在由TestContext
框架管理的模拟会话中配置主题。以下示例显示了如何执行此操作:
Session作用域bean配置
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:userPreferences-ref="userPreferences" />
<bean id="userPreferences" class="com.example.UserPreferences"
c:theme="#{session.getAttribute('theme')}"
scope="session">
<aop:scoped-proxy/>
</bean>
</beans>
在SessionScopedBeanTests
中,我们将UserService
和MockHttpSession
注入我们的测试实例。在sessionScope()
测试方法中,我们通过在提供的MockHttpSession
中设置期望的主题属性来设置测试配置。当在我们的userService
上调用processUserPreferences()
方法时,我们可以确保用户服务可以访问当前MockHttpSession
的会话范围的userPreferences
,并且可以基于配置的主题对结果执行断言。以下示例显示了如何执行此操作:
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpSession session;
@Test
void sessionScope() throws Exception {
session.setAttribute("theme", "blue");
Results results = userService.processUserPreferences();
// assert results
}
}
参考代码:
org.liyong.test.annotation.test.spring.SessionScopedBeanTests
3.5.8 事物管理
在TestContext
框架中,事务由TransactionalTestExecutionListener
进行管理,默认情况下配置该事务,即使你没有在测试类上显示声明@TestExecutionListeners
也不例外。但是,要启用对事务的支持,必须在ApplicationContext
中配置使用@ContextConfiguration
语义加载的PlatformTransactionManager
bean(稍后将提供更多详细信息)。此外,你必须在测试的类或方法级别声明Spring的@Transactional
注解。
测试事物管理
测试管理的事务是通过使用TransactionalTestExecutionListener
声明式管理的事务,或者是通过使用TestTransaction
以编程方式管理的事务(稍后描述)。你不应将此类事务与Spring托管的事务(由Spring在加载的Test中直接管理的事务)或应用程序托管的事务(在测试调用的应用程序代码中以编程方式管理的事务)混淆。Spring管理的事务和应用程序管理的事务通常参与测试管理的事务。但是,如果Spring管理的事务或应用程序管理的事务配置除REQUIRED
或SUPPORTS
之外的任何传播类型,则应谨慎使用(有关详细信息,请参见关于事务传播的讨论)。
抢占式超时和测试管理事务
当在测试框架中使用任何形式的抢占式超时并结合使用Spring的测试管理事务时,必须谨慎。
具体来说,Spring的测试支持在调用当前测试方法之前将事务状态绑定到当前线程(通过
java.lang.ThreadLocal
变量)。如果测试框架在新线程中调用当前测试方法以支持抢占式超时,则在当前测试方法内执行的任何操作都不会在测试管理的事务内调用。因此,任何此类操作的结果都不会随测试管理的事务回滚。相反,即使Spring适当地回滚了测试管理的事务,此类操作仍将提交给持久性存储(例如关系数据库)。可能发生这种情况的情况包括但不限于以下情况。
- JUnit 4的
@Test(timeout =…)
支持和TimeOut
规则org.junit.jupiter.api.Assertions
类中的JUnit Jupiter的assertTimeoutPreemptively(…)
方法- TestNG的
@Test(timeOut =…)
支持
激活和禁止事物
默认情况下,使用@Transactional
注解测试方法会导致测试在事务中运行,该事务在测试完成后会自动回滚。如果用@Transactional
注解测试类,则该类层次结构中的每个测试方法都在事务中运行。未用@Transactional
注解的测试方法(在类或方法级别)不在事务内运行。请注意,测试生命周期方法不支持@Transactional
,例如,使用JUnit Jupiter的@BeforeAll
,@BeforeEach
等进行注解的方法。此外,用@Transactional
注解但传播属性设置为NOT_SUPPORTED
的测试不在事务内运行。
@Transactional
支持属性表格:
属性 | 测试管理事务的支持 |
---|---|
value 和 transactionManager |
yes |
propagation |
仅仅支持Propagation.NOT_SUPPORTED |
isolation |
no |
timeout |
no |
readOnly |
no |
rollbackFor 和 rollbackForClassName |
no : 使用TestTransaction.flagForRollback() 替换 |
noRollbackFor 和 noRollbackForClassName |
no : 使用TestTransaction.flagForCommit() 替换 |
方法级生命周期方法(例如,用JUnit Jupiter的
@BeforeEach
或@AfterEach
注解的方法)在测试管理的事务中运行。另一方面,套件级和类级生命周期方法(例如,以JUnit Jupiter的@BeforeAll
或@AfterAll
注解的方法以及以TestNG的@BeforeSuite
、@AfterSuite
、@BeforeClass
或@AfterClass
注解的方法)不在内部运行测试管理的事务。如果你需要在事务内的套件级或类级生命周期方法中执行代码,则可能希望将相应的
PlatformTransactionManager
注入测试类,然后将其与TransactionTemplate
一起用于程序化事务管理。
请注意,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests已预先配置为在类级别提供事务支持。
下面的示例演示了为基于Hibernate
的UserRepository
编写集成测试的常见方案:
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
void createUser() {
// track initial state in test database:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
assertNumUsers(count + 1);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
如“事务回滚和提交行为”中所述,运行createUser()
方法后无需清理数据库,因为对数据库所做的任何更改都会由TransactionalTestExecutionListener
自动回滚。
参考代码:
org.liyong.test.annotation.test.spring.TransactionMergeUserRepositoryTests
和org.liyong.test.annotation.test.spring.TransactionUserRepositoryTests
事物回滚和提交
默认情况下,测试事务将在测试完成后自动回滚;但是,可以通过@Commit
和@Rollback
注解声明性地配置事务@Commit
和@Rollback
行为。有关更多详细信息,请参见注解支持部分中的对应条目。
参考代码:
org.liyong.test.annotation.test.spring.AnnotationTransactionManagementTests
编程式事物管理
你可以使用TestTransaction
中的静态方法以编程方式与测试管理的事务进行交互。例如,可以在测试方法中、方法之前和方法之后使用TestTransaction
来启动或结束当前的测试托管事务,或配置当前的测试托管事务以进行回滚或提交。每当启用TransactionalTestExecutionListener
时,都会自动提供对TestTransaction
的支持。下面的示例演示了TestTransaction
的某些功能。有关更多详细信息,请参见javadoc中的TestTransaction。
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// assert initial state in test database:
assertNumUsers(2);
deleteFromTables("user");
// changes to the database will be committed!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
参考代码:
org.liyong.test.annotation.test.spring.ProgrammaticTransactionManagementTests
在事务外运行代码
有时,你可能需要在事务测试方法之前或之后但在事务上下文之外执行某些代码。例如,在运行测试之前验证初始数据库状态或在测试运行之后验证预期的事务提交行为(如果测试已配置为提交事务)。
对于此类情况,TransactionalTestExecutionListener
支持@BeforeTransaction
和@AfterTransaction
注解。你可以使用这些注解之一来注释测试类中的任何void
方法或测试接口中的任何void
默认方法,并且TransactionalTestExecutionListener
可以确保你的before
事务方法或after
事务方法在适当的时间运行。
任何
before
方法(例如,以JUnit Jupiter的@BeforeEach
注解的方法)和任何after
方法(例如,以JUnit Jupiter的@AfterEach
注解的方法)都在事务中运行。此外,对于未配置为在事务内运行的测试方法,不会运行带有@BeforeTransaction
或@AfterTransaction
注解的方法。
配置事物管理器
TransactionalTestExecutionListener
期望在Spring ApplicationContext
中为测试定义一个PlatformTransactionManager
bean。如果测试的ApplicationContext
中存在PlatformTransactionManager
的多个实例,则可以使用@Transactional("myTxMgr")
或@Transactional(transactionManager ="myTxMgr")
声明限定符,或者可以通过@Configuration
类实现TransactionManagementConfigurer
。有关用于在测试的ApplicationContext
中查找事务管理器的算法的详细信息,请查阅Javadoc中的TestContextTransactionUtils.retrieveTransactionManager()。
演示所有与事务相关的注解
以下基于JUnit Jupiter的示例显示了一个虚拟的集成测试方案,该方案突出显示了所有与事务相关的注解。该示例并非旨在演示最佳实践,而是演示如何使用这些注解。有关更多信息和配置示例,请参见注解支持部分。@Sql
的事务管理包含一个附加示例,该示例使用@Sql
执行具有默认事务回滚语义的声明性SQL脚本。下面的示例显示了相关的注解。
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
void tearDownWithinTransaction() {
// execute "tear down" logic within the transaction
}
@AfterTransaction
void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
测试ORM代码时避免误报
当你测试操纵
Hibernate
会话或JPA持久性上下文状态的应用程序代码时,请确保在运行该代码的测试方法中刷新基础工作单元。未能刷新基本工作单元可能会产生误报:你的测试通过了,但是相同的代码在实际的生产环境中引发了异常。请注意,这适用于任何维护内存中工作单元的ORM框架。在下面的基于Hibernate的示例测试用例中,一种方法演示了误报,另一种方法正确地公开了刷新会话的结果:// ... @Autowired SessionFactory sessionFactory; @Transactional @Test // no expected exception! public void falsePositive() { updateEntityInHibernateSession(); // False positive: an exception will be thrown once the Hibernate // Session is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) public void updateWithSessionFlush() { updateEntityInHibernateSession(); // Manual flush is required to avoid false positive in test sessionFactory.getCurrentSession().flush(); } // ...
以下示例显示了JPA的匹配方法:
// ... @PersistenceContext EntityManager entityManager; @Transactional @Test // no expected exception! public void falsePositive() { updateEntityInJpaPersistenceContext(); // False positive: an exception will be thrown once the JPA // EntityManager is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) public void updateWithEntityManagerFlush() { updateEntityInJpaPersistenceContext(); // Manual flush is required to avoid false positive in test entityManager.flush(); } // ...
3.5.9 执行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
可能比后面描述的其他一些替代方案更适合你的需要。有关更多细节,请参阅javadoc以了解ScriptUtils
中的各个方法。
ResourceDatabasePopulator
提供了一个基于对象的API,用于使用在外部资源中定义的SQL脚本以编程方式填充、初始化或清理数据库。ResourceDatabasePopulator
提供了一些选项,用于配置解析和运行脚本时使用的字符编码、语句分隔符、注释分隔符和错误处理标志。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参阅javadoc。要运行ResourceDatabasePopulator
中配置的脚本,可以调用populate(Connection)
方法对java.sql.Connection
执行填充器,或者execute(DataSource)
方法对javax.sql.DataSource
执行填充器。以下示例为测试schema
和测试数据指定SQL脚本,将语句分隔符设置为@@
,然后针对数据源执行脚本:
@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);
// execute code that uses the test schema and data
}
请注意,ResourceDatabasePopulator
内部委托给ScriptUtils
来解析和运行SQL脚本。同样,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests中的executeSqlScript(..)
方法在内部使用ResourceDatabasePopulator
运行SQL脚本。有关各种executeSqlScript(..)
方法的详细信息,请参阅javadoc。
参考代码:
org.liyong.test.annotation.test.spring.TransactionUserRepositoryTests
使用@Sql声明式地执行SQL脚本
除了上述用于以编程方式运行SQL脚本的机制之外,你还可以在Spring TestContext
框架中声明性地配置SQL脚本。具体来说,你可以在测试类或测试方法上声明@Sql
注解,以配置单独的SQL语句或应在集成测试方法之前或之后针对给定数据库运行的SQL脚本的资源路径。@Sql
的支持由SqlScriptsTestExecutionListener
提供,默认情况下启用。
方法级别的
@Sql
声明默认情况下覆盖类级别的声明。从Spring框架5.2开始,可以通过@SqlMergeMode
为每个测试类或每个测试方法配置此行为。有关更多详细信息,请参见使用@SqlMergeMode合并和覆盖配置。
路径资源语义
每个路径都被解释为Spring资源。文本路径(例如,schema.sql
)被视为相对于定义测试类的包的类路径资源。以斜杠开头的路径被视为绝对类路径资源(例如,/org/example/schema.sql
)。通过使用指定的资源协议加载引用URL的路径(例如,以classpath:
、file:
、http:
为前缀的路径)。以下示例显示了如何在基于JUnit Jupiter的集成测试类中的类级别和方法级别使用@Sql
:
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void emptySchemaTest() {
// execute code that uses the test schema without any test data
}
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// execute code that uses the test schema and test data
}
}
参考代码:
org.liyong.test.annotation.test.spring.InferredDataSourceTransactionalSqlScriptsTests
默认脚本检测
如果未指定任何SQL脚本或语句,则根据声明@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的可重复注解:
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
// execute code that uses the test schema and test data
}
在前面的示例中呈现的方案中,test-schema.sql
脚本对单行注解使用了不同的语法。
除了@Sql
声明在@SqlGroup
中分组在一起之外,以下示例与上述示例相同。在Java 8及更高版本中,使用@SqlGroup
是可选的,但是你可能需要使用@SqlGroup
与其他JVM语言(例如Kotlin)兼容。
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// execute code that uses the test schema and test data
}
脚本执行阶段
默认情况下,SQL脚本在相应的测试方法之前执行。但是,如果需要在测试方法之后运行一组特定的脚本(例如,清理数据库状态),则可以使用@Sql
中的executionPhase
属性,如以下示例所示:
@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() {
// execute code that needs the test data to be committed
// to the database outside of the test's transaction
}
请注意,分别从Sql.TransactionMode
和Sql.ExecutionPhase
静态导入了ISOLATED
和AFTER_TEST_METHOD
。
@SqlConfig脚本配置
你可以使用@SqlConfig
注解配置脚本解析和错误处理。当在集成测试类上声明为类级别的注解时,@SqlConfig
充当测试类层次结构中所有SQL脚本的全局配置。通过使用@Sql
注解的config
属性直接声明时,@SqlConfig
用作封闭的@Sql
注解中声明的SQL脚本的本地配置。@SqlConfig
中的每个属性都有一个隐式默认值,该默认值记录在相应属性的javadoc中。不幸的是,由于Java语言规范中为注解属性定义了规则,因此无法将null
值分配给注解属性。因此,为了支持对继承的全局配置的覆盖,@SqlConfig
属性的显式默认值为“”
(对于字符串)、{}
(对于数组)或DEFAULT
(对于枚举)。这种方法允许@SqlConfig
的本地声明通过提供除“”
、{}
或DEFAULT
之外的值来有选择地覆盖@SqlConfig
的全局声明中的各个属性。只要本地@SqlConfig
属性不提供除“”
、{}
或DEFAULT
以外的显式值,就会继承全局@SqlConfig
属性。因此,显式本地配置将覆盖全局配置。
@Sql
和@SqlConfig
提供的配置选项与ScriptUtils
和ResourceDatabasePopulator
支持的配置选项等效,但是,是<jdbc:initialize-database />
XML名称空间元素提供的配置选项的超集。有关详细信息,请参见@Sql和@SqlConfig中各个属性的javadoc。
@Sql事物管理
默认情况下,SqlScriptsTestExecutionListener
会为使用@Sql
配置的脚本推断所需的事务语义。具体来说,SQL脚本不是在没有事务的情况下运行,而是在现有的Spring管理的事务中运行(例如,由TransactionalTestExecutionListener
管理的事务,以@Transactional
进行注解的测试),或者在隔离的事务中运行,具体取决于transactionMode
的配置值@SqlConfig
中的属性,以及测试的ApplicationContext
中是否存在PlatformTransactionManager
。但是,作为最低要求,测试的ApplicationContext
中必须存在一个javax.sql.DataSource
。
如果SqlScriptsTestExecutionListener
用来检测DataSource
和PlatformTransactionManager
并推断事务语义的算法不符合你的需求,则可以通过设置@SqlConfig
的dataSource
和transactionManager
属性来指定显式名称。此外,你可以通过设置@SqlConfig
的transactionMode
属性来控制事务传播行为(例如,是否应在隔离的事务中运行脚本)。虽然彻底讨论事务管理的所有支持的选项@Sql
超出了这个范围参考手册,@SqlConfig 和SqlScriptsTestExecutionListener javadoc提供详细信息,和下面的示例显示了一个典型的测试场景中,使用JUnit Jupiter和事务与@Sq
l测试:
@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);
// execute 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.");
}
}
请注意,在运行usersTest()方
法之后,无需清理数据库,因为对数据库所做的任何更改(在test
方法中或在/test-data.sql
脚本中)都将自动回滚。 TransactionalTestExecutionListener
(有关详细信息,请参见事务管理)。
使用@SqlMergeMode合并和覆盖配置
从Spring框架5.2开始,可以将方法级@Sql
声明与类级声明合并。例如,这允许你为每个测试类提供一次数据库schema
的配置或一些常见的测试数据,然后为每种测试方法提供特定于用例的其他测试数据。若要启用@Sql
合并,请使用@SqlMergeMode(MERGE)
注解测试类或测试方法。若要禁用特定测试方法(或特定测试子类)的合并,可以通过@SqlMergeMode(OVERRIDE)
切换回默认模式。有关示例和更多详细信息,请查阅@SqlMergeMode注解文档部分。
3.5.10 并行测试执行
Spring框架5.0引入了对使用Spring TestContext
框架时在单个JVM中并行执行测试的基本支持。通常,这意味着大多数测试类或测试方法可以并行执行,而无需更改测试代码或配置。
有关如何设置并行测试执行的详细信息,请参见你的测试框架,构建工具或IDE的文档。
请记住,将并发引入测试套件可能会导致意外的副作用,奇怪的运行时行为以及间歇性或看似随机失败的测试。因此,Spring团队为何时不并行执行测试提供了以下一般指导。
如果测试符合以下条件,则不要并行执行测试:
- 使用Spring框架的
@DirtiesContext
支持。 - 使用Spring Boot的
@MockBean
或@SpyBean
支持。 - 使用JUnit 4的
@FixMethodOrder
支持或旨在确保测试方法按特定顺序运行的任何测试框架功能。但是请注意,如果整个测试类是并行执行的,则此方法不适用。 - 更改共享服务或系统(如数据库,消息代理,文件系统等)的状态。这适用于嵌入式和外部系统。
如果并行测试执行失败,并有异常指出当前测试的
ApplicationContext
不再处于活动状态,则这通常意味着ApplicationContext
已从ContextCache
中的另一个线程中删除。这可能是由于使用
@DirtiesContext
或由于从ContextCache
自动驱逐。如果@DirtiesContext
是罪魁祸首,则需要找到一种避免使用@DirtiesContext
的方法,或者从并行执行中排除此类测试。如果已超过ContextCache
的最大大小,则可以增加缓存的最大大小。有关详细信息,请参见上下文缓存的讨论。Spring
TestContext
框架中的并行测试执行只有在底层的TestContext
实现提供了副本构造函数的情况下才可能执行,如TestContext的javadoc中所述。Spring中使用的DefaultTestContext
提供了这样的构造函数。但是,如果你使用提供自定义TestContext
实现的第三方库,则需要验证它是否适合并行测试执行。
3.5.11 TestContext 框架支持类
本节描述了支持Spring TestContext
框架的各种类。
Spring JUnit 4 Runner
Spring TestContext
框架通过自定义运行程序(在JUnit 4.12或更高版本上受支持)提供了与JUnit 4的完全集成。通过使用@RunWith(SpringJUnit4ClassRunner.class)
或简写的@RunWith(SpringRunner.class)
注释测试类,开发人员可以实现基于JUnit 4的标准单元测试和集成测试,同时获得TestContext
框架的优势,例如对加载应用程序上下文、测试实例的依赖关系注入、事务性测试方法执行等。如果你想将Spring TestContext
框架与替代运行程序(例如JUnit 4的Parameterized
运行程序)或第三方运行程序(例如MockitoJUnitRunner
)一起使用,则可以选择使用Spring对JUnit规则的支持。
以下代码清单显示了配置测试类与自定义Spring Runner一起运行的最低要求:
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// execute test logic...
}
}
在前面的示例中,为@TestExecutionListeners
配置了一个空列表以禁用默认监听器,否则将需要通过@ContextConfiguration
配置ApplicationContext
。
Spring JUnit4规则
org.springframework.test.context.junit4.rules
包提供以下JUnit 4规则(在JUnit 4.12或更高版本上受支持):
SpringClassRule
SpringMethodRule
SpringClassRule
是一个JUnit TestRule
,它支持Spring TestContext
框架的类级功能,而SpringMethodRule
是一个JUnit MethodRule
,它支持Spring TestContext
框架的实例级和方法级功能。
与SpringRunner
相比,Spring的基于规则的JUnit支持具有独立于任何org.junit.runner.Runner
实现的优点,因此可以与现有的替代运行器(例如JUnit 4的Parameterized
)或第三方结合使用运行器(例如MockitoJUnitRunner)。
为了支持TestContext
框架的全部功能,必须将SpringClassRule
与SpringMethodRule
结合使用。以下示例显示了在集成测试中声明这些规则的正确方法:
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// execute test logic...
}
}
JUnit 4 Support支持类
org.springframework.test.context.junit4
包为基于JUnit 4的测试用例提供了以下支持类(在JUnit 4.12或更高版本上受支持):
AbstractJUnit4SpringContextTests
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
是抽象的基础测试类,该类将Spring TestContext
框架与JUnit 4环境中的显式ApplicationContext
测试支持集成在一起。扩展AbstractJUnit4SpringContextTests
时,可以访问protected applicationContext
实例变量,该变量可用于执行显式bean查找或测试整个上下文的状态。
AbstractTransactionalJUnit4SpringContextTests
是AbstractJUnit4SpringContextTests
的抽象事务扩展,为JDBC访问添加了一些便利功能。此类期望在ApplicationContext
中定义一个javax.sql.DataSource
bean和PlatformTransactionManager
bean。扩展AbstractTransactionalJUnit4SpringContextTests
时,可以访问protected jdbcTemplate
实例变量,该实例变量可用于运行SQL语句来查询数据库。你可以在运行与数据库相关的应用程序代码之前和之后使用此类查询来确认数据库状态,并且Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具一起使用时,请确保避免误报。如JDBC测试支持中所述,AbstractTransactionalJUnit4SpringContextTests
还提供了方便的方法,这些方法通过使用上述的jdbcTemplate
委托给JdbcTestUtils
中的方法。此外,AbstractTransactionalJUnit4SpringContextTests
提供了executeSqlScript(..)
方法,用于针对已配置的DataSource
运行SQL脚本。
这些类为扩展提供了便利。如果你不希望将测试类绑定到特定于Spring的类层次结构,则可以使用
@RunWith(SpringRunner.class)
或Spring的JUnit规则来配置自己的自定义测试类。
JUnit Jupiter的SpringExtension
Spring TestContext
框架提供了与JUnit 5中引入的JUnit Jupiter测试框架的完全集成。通过使用@ExtendWith(SpringExtension.class)
注解测试类,你可以实现基于JUnit Jupiter的标准单元测试和集成测试,并同时获得TestContext
框架的好处,例如支持加载应用程序上下文、测试实例的依赖注入、事务性测试方法执行等等。
此外,得益于JUnit Jupiter中丰富的扩展API,Spring在Spring支持JUnit 4和TestNG的功能集之外提供了以下功能:
- 测试构造函数、测试方法和测试生命周期回调方法的依赖注入。有关更多详细信息,请参见使用SpringExtension进行依赖注入。
-
对基于SpEL表达式、环境变量、系统属性等的条件测试执行的强大支持。有关更多详细信息和示例,请参见Spring JUnit Jupiter测试注解中有关
@EnabledIf
和@DisabledIf
的文档。 - 自定义组合注解,结合了Spring和JUnit Jupiter的注解。有关更多详细信息,请参见“元注解支持测试”中的
@TransactionalDevTestConfig
和@TransactionalIntegrationTest
示例。
以下代码清单显示如何配置测试类以将SpringExtension
与@ContextConfiguration
结合使用:
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// execute test logic...
}
}
由于你还可以在JUnit 5中将注解用作元注解,因此Spring提供了@SpringJUnitConfig
和@SpringJUnitWebConfig
组成的注解,以简化测试ApplicationContext
和JUnit Jupiter的配置。
以下示例使用@SpringJUnitConfig
减少前一示例中使用的配置量:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// execute test logic...
}
}
同样,以下示例使用@SpringJUnitWebConfig
创建用于JUnit Jupiter的WebApplicationContext
:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
@Test
void testMethod() {
// execute test logic...
}
}
有关更多详细信息,请参见Spring JUnit Jupiter测试注解中有关@SpringJUnitConfig
和@SpringJUnitWebConfig的文档。
使用SpringExtension
依赖注入
SpringExtension
从JUnit Jupiter实现了ParameterResolver扩展API,它使Spring可以为测试构造函数、测试方法和测试生命周期回调方法提供依赖项注入。
具体来说,SpringExtension
可以将来自测试ApplicationContext
的依赖项注入到使用@BeforeAll
、@AfterAll
、@BeforeEach
、@AfterEach
、@Test
、@RepeatedTest
、@ParameterizedTest
等标记的测试构造函数和方法中。
构造函数注入
如果JUnit Jupiter测试类的构造函数中的特定参数为ApplicationContext
类型(或其子类型)、或者使用@Autowired
、@Qualifier
或@Value进行注解或元注解,则Spring会为此特定值注入值参数与测试的ApplicationContext
中的相应bean或值。
如果认为构造函数是可自动装配的,则还可以将Spring配置为自动连接测试类构造函数的所有参数。如果满足以下条件之一(按优先顺序),则认为构造函数是可自动构造的。
- 构造函数带有
@Autowired
注解。 @TestConstructor
在autowireMode
属性设置为ALL的情况下出现在测试类中或以元形式出现。- 默认的测试构造函数自动装配模式已更改为ALL。
有关使用@TestConstructor以及如何更改全局测试构造函数自动装配模式的详细信息,请参见@TestConstructor
。
如果测试类的构造函数被认为是可自动装配的,则Spring负责解析构造函数中所有参数的参数。因此,在JUnit Jupiter中注册的其他
ParameterResolver
不能解析此类构造函数的参数。如果在测试方法之前或之后使用
@DirtiesContext
关闭测试的ApplicationContext
,则不得与JUnit Jupiter的@TestInstance(PER_CLASS)
支持一起使用针对测试类的构造函数注入。原因是
@TestInstance(PER_CLASS)
指示JUnit Jupiter在测试方法调用之间缓存测试实例。因此,测试实例将保留对最初从随后已关闭的ApplicationContext
注入的bean的引用。由于在这种情况下测试类的构造函数将仅被调用一次,因此依赖注入不会再次发生,并且后续测试将与关闭的ApplicationContext
中的bean进行交互,这可能会导致错误。要将
@DirtiesContext
与@TestInstance(PER_CLASS)
一起用于“测试方法之前
”或“测试方法之后
”模式,必须配置通过字段或setter
注入提供的Spring依赖项,以便可以在测试之间重新注入它们方法调用。
在下面的示例中,Spring将从TestConfig.class
加载的ApplicationContext
中的OrderService
bean注入OrderServiceIntegrationTests
构造函数中。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
请注意,此功能使测试依赖项是最终的,因此是不可变的。
如果spring.test.constructor.autowire.mode
属性是all
(请参阅@TestConstructor),则可以在上一个示例中省略构造函数上@Autowired
的声明,从而得到以下结果。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
方法注入
如果JUnit Jupiter测试方法或测试生命周期回调方法中的参数属于ApplicationContext
类型(或其子类型),或者使用@Autowired
、@Qualifier
或@Value
进行注解或元注解,则Spring会为此注入值。特定参数以及来自测试的ApplicationContext
的相应bean。
在下面的示例中,Spring从TestConfig.class
加载的ApplicationContext
中将OrderService
注入到deleteOrder()
测试方法中:
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
由于JUnit Jupiter中ParameterResolver
支持的强大功能,你不仅可以从Spring中,而且可以从JUnit Jupiter本身或其他第三方扩展中,将多个依赖项注入到单个方法中。
下面的示例演示如何让Spring和JUnit Jupiter同时将依赖项注入到placeOrderRepeatedly()
测试方法中。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
注意,通过使用JUnit Jupiter中的@RepeatedTest
,测试方法可以访问RepetitionInfo
。
TestNG支持类
org.springframework.test.context.testng
包为基于TestNG的测试用例提供以下支持类:
AbstractTestNGSpringContextTests
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests
是抽象的基础测试类,该类将Spring TestContext
框架与TestNG环境中的显式ApplicationContext
测试支持集成在一起。扩展AbstractTestNGSpringContextTests
时,可以访问protected applicationContext
实例变量,该变量可用于执行显式bean查找或测试整个上下文的状态。
AbstractTransactionalTestNGSpringContextTests
是AbstractTestNGSpringContextTests
的抽象事务扩展,为JDBC访问添加了一些便利功能。此类期望在ApplicationContext
中定义一个javax.sql.DataSource
bean和PlatformTransactionManager
bean。扩展AbstractTransactionalTestNGSpringContextTests
时,可以访问protected jdbcTemplate
实例变量,该实例变量可用于执行SQL语句来查询数据库。你可以在运行与数据库相关的应用程序代码之前和之后使用此类查询来确认数据库状态,并且Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具一起使用时,请确保避免误报。如JDBC测试支持中所述,AbstractTransactionalTestNGSpringContextTests
还提供了方便的方法,这些方法通过使用上述的jdbcTemplate
委托给JdbcTestUtils
中的方法。此外,AbstractTransactionalTestNGSpringContextTests
提供了executeSqlScript(..)
方法,用于针对已配置的DataSource
运行SQL脚本。
这些类为扩展提供了便利。如果你不希望将测试类绑定到特定于Spring的类层次结构,则可以使用
@ContextConfiguration
、@TestExecutionListeners
等来配置自己的自定义测试类,并通过使用TestContextManager
手动检测测试类。有关如何检测测试类的示例,请参见AbstractTestNGSpringContextTests
的源代码。