本节(本章其余部分)涵盖了Spring应用程序的集成测试。它包括以下主题:
3.1 概要
能够执行一些集成测试而无需部署到应用程序服务器或连接到其他企业基础结构,这一点很重要。这样可以测试以下内容:
- 正确连接Spring IoC容器上下文。
- 使用JDBC或ORM工具进行数据访问。这可以包括诸如SQL语句的正确性、Hibernate查询、JPA实体映射之类的东西。
Spring框架为Spring测试模块中的集成测试提供了一流的支持。实际的JAR文件的名称可能包括发行版,也可能采用长org.springframework.test
格式,具体取决于你从何处获取(请参阅“依赖管理”部分中的说明)。该库包含org.springframework.test
包,其中包含用于与Spring容器进行集成测试的有价值的类。此测试不依赖于应用程序服务器或其他部署环境。此类测试的运行速度比单元测试慢,但比依赖于部署到应用程序服务器的等效Selenium测试或远程测试快。
单元和集成测试支持以注解驱动的Spring TestContext 框架的形式提供。TestContext
框架与实际使用的测试框架无关,该框架允许各种环境(包括JUnit,TestNG和其他环境)中对测试进行检测。
3.2 集成测试目标
Spring的集成测试支持的主要目标如下:
- 在测试之间管理Spring IoC容器缓存。
- 提供测试fixture实例的依赖注入。
- 提供适合集成测试的事务管理。
- 提供特定于Spring的基类,以帮助开发人员编写集成测试
接下来的几节描述了每个目标,并提供了有关实现和配制详细信息的链接。
Fixture意思:JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture
3.2.1 上下文管理和缓存
Spring TestContext
框架提供了Spring ApplicationContext
实例和WebApplicationContext
实例的一致加载以及这些上下文的缓存。支持加载上下文的缓存很重要,因为启动时间可能会成为一个问题-不是因为Spring本身的开销,而是因为Spring容器实例化的对象需要时间才能实例化。例如,具有50到100个Hibernate
映射文件的项目可能需要10到20秒来加载映射文件,并且在每个测试fixture中运行每个测试之前要承担该消耗,这会导致整体测试运行速度变慢,从而降低了开发人员的工作效率。
测试类通常声明XML或Groovy配置元数据的资源位置数组(通常是在类路径中)或用于配置应用程序的组件类的数组。这些位置或类与web.xml
或其他用于生产部署的配置文件中指定的位置或类相同或相似。
默认情况下,加载后,已配置的ApplicationContext
将重新用每个测试。因此,每个测试套件仅产生一次安装成本,并且随后的测试执行要快得多。在这种情况下,术语“测试套件”是指所有测试都在相同JVM中运行,例如,所有测试都从给定项目或模块的Ant
、Maven
或Gradle
构建运行。在不太可能的情况下,测试破坏了应用程序上下文并需要重新加载(例如,通过修改bean定义或应用程序对象的状态),可以将TestContext
框架配置为重新加载配置并重建应用程序上下文,然后再执行下一个测试。
请参见使用TestContext框架进行上下文管理和上下文缓存。
3.2.2 测试装置的依赖注入
当TestContext
框架加载你的应用程序上下文时,可以选择地用依赖注入来配置测试类的实例。这提供了一种方便的机制,可以通过在应用程序上下文中使用预配置的bean来设置测试fixture
。这里一个强大的好处是,你可以跨各种测试场景重用应用程序上下文(例如,用于配置spring管理的对象图、事务代理、数据源实例等),从而避免为单个测试用例重复复杂的测试fixture
设置。
例如,考虑一个场景,其中我们有一个类(HibernateTitleRepository
),该类为Title域实体实现数据访问逻辑。我们要编写集成测试来测试以下方面:
- Spring配置:基本上,与
HibernateTitleRepository
bean的配置有关的一切都正确并存在吗? -
Hibernate
映射文件配置:是否已正确映射所有内容,并且是否有正确的延迟加载配置? -
HibernateTitleRepository
的逻辑:此类的配置实例是否按预期执行?
请参见使用TestContext框架进行测试fixture
的依赖注入。
3.2.3 事物管理
访问真实数据库的测试中的一个常见问题是它们对持久存储状态的影响。即使使用开发数据库,对状态的更改也可能会影响以后的测试。同样,许多操作(例如插入或修改持久数据)无法在事物之外执行(或验证)。
TestContext
框架解决了这个问题。默认情况下,框架为每个测试创建并回滚事务。你可以编写可以假定存在事务的代码。如果在测试中调用事务代理对象,则对象将根据其配置事务语义正确运行。此外,如果测试方法在测试管理的事务中运行时删除了选定表的内容,则该事务将默认回滚,并且数据库将返回到执行测试之前的状态。通过使用在测试的应用程序上下文中定义的PlatformTransactionManager
bean,可以为测试提供事务支持。
如果你想要提交一个事务(不常见,但是当你想要一个特定的测试填充或修改数据库时,偶尔会有用),你可以通过使用@Commit
注解告诉TestContext
框架使事务提交而不是回滚。
请参阅使用TestContext框架进行事务管理。
3.2.4 集成测试支持的类
Spring TestContext
框架提供了几个抽象支持类,这些基础测试类为测试框架提供了定义明确的钩子,以方便的实例变量和方法,可用于访问以下内容:
- ApplicationContext,用于执行显式的bean查找或测试整个上下文的状态。
- 一个JdbcTemplate,用于执行SQL语句来查询数据库。你可以使用此类查询在执行与数据库相关的应用程序代码之前和之后确认数据库状态,并且Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具一起使用时,请确保避免误报。
另外,你可能希望使用针对你的项目的实例变量和方法构建自己的自定义,应用程序范围的超类。
请参阅TestContext框架的支持类。
3.3 JDBC测试支持
org.springframework.test.jdbc
包包含JdbcTestUtils
,它是JDBC相关实用程序功能的集合,旨在简化标准数据库测试方案。具体来说,JdbcTestUtils
提供以下静态实用程序方法。
-
countRowsInTable(..)
:计算给定表中的行数。 -
countRowsInTableWhere(..)
:使用提供的WHERE
子句计算给定表中的行数。 -
deleteFromTables(..)
:删除指定表中的所有行。 -
deleteFromTableWhere(..)
: 使用提供的WHERE
子句从给定表中删除行。
AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
提供了便利的方法,这些方法委托给JdbcTestUtils
中的上述方法。spring-jdbc
模块提供了对配置和启动嵌入式数据库的支持你可以在与数据库交互的集成测试中使用它。有关详细信息,请参见嵌入式数据库支持和使用嵌入式数据库测试数据访问逻辑。
3.4 注解
本节介绍了在测试Spring应用程序时可以使用的注解。它包括以下主题:
3.4.1 Spring测试注解
Spring框架提供了以下特定于Spring的注解集,你可以在单元测试和集成测试中将它们与TestContext
框架结合使用。有关更多信息,请参见相应的javadoc,包括默认属性值、属性别名和其他详细信息。
Spring的测试注解包括以下内容:
@BootstrapWith
@ContextConfiguration
@WebAppConfiguration
@ContextHierarchy
@ActiveProfiles
@TestPropertySource
@DynamicPropertySource
@DirtiesContext
@TestExecutionListeners
@Commit
@Rollback
@BeforeTransaction
@AfterTransaction
@Sql
@SqlConfig
@SqlMergeMode
@SqlGroup
@BootstrapWith
@BootstrapWith
是一个类级别的注解,可用于配置如何引导Spring TestContext
框架。具体来说,你可以使用@BootstrapWith
指定自定义TestContextBootstrapper
。有关更多详细信息,请参见有关引导TestContext框架的部分。
参考代码:
org.liyong.test.annotation.test.spring.ConfigClassApplicationContextTests
@ContextConfiguration
@ContextConfiguration
定义了用于确定如何为集成测试加载和配置ApplicationContext
的类级元数据。具体来说,@ContextConfiguration
声明应用程序上下文资源位置或用于加载上下文的组件类。
资源位置通常是位于类路径中的XML配置文件或Groovy脚本,而组件类通常是@Configuration
类。但是,资源位置也可以引用文件系统中的文件和脚本,组件类可以是@Component
类、@Service
类等等。有关更多详细信息,请参见组件类。
以下示例显示了一个指向XML文件的@ContextConfiguration
注解:
@ContextConfiguration("/test-config.xml") //
class XmlApplicationContextTests {
// class body...
}
- 引用XML文件。
以下示例显示了一个@ContextConfiguration
注解,该注解引用了一个类:
@ContextConfiguration(classes = TestConfig.class) //1
class ConfigClassApplicationContextTests {
// class body...
}
- 引用类文件
参考代码:
org.liyong.test.annotation.test.spring.ConfigClassApplicationContextTests
作为声明资源位置或组件类的替代方法或补充,可以使用@ContextConfiguration
声明ApplicationContextInitializer
类。以下示例显示了这种情况:
@ContextConfiguration(initializers = CustomContextIntializer.class)
class ContextInitializerTests {
// class body...
}
参考代码:
org.liyong.test.annotation.test.spring.ContextInitializerTests
你可以选择使用@ContextConfiguration
来声明ContextLoader
策略。但是,你通常不需要显式配置加载器,因为默认加载器支持初始化程序以及资源位置或组件类。
以下示例同时使用配置位置和加载器:
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) //1
class CustomLoaderXmlApplicationContextTests {
// class body...
}
- 配置位置和自定义加载器。
@ContextConfiguration
为继承资源位置或配置类以及超类声明上下文初始化器提供支持。
有关更多详细信息,请参见上下文管理和@ContextConfiguration
javadocs。
参考代码:
org.liyong.test.annotation.test.spring.CustomLoaderXmlApplicationContextTests
@WebAppConfiguration
@WebAppConfiguration
是一个类级别的注解,可用于声明为集成测试加载的ApplicationContext
应该是WebApplicationContext
。@WebAppConfiguration
仅存在于测试类上,可以确保为测试加@WebApplicationContext
,并使用默认值file:src/main/webapp
作为Web应用程序根目录(也就是即资源基本路径)。资源基础路径用于在后台创建MockServletContext
,该MockServletContext
用作测试的WebApplicationContext
的ServletContext
。
以下示例显示了如何使用@WebAppConfiguration
注解:
@ContextConfiguration
@WebAppConfiguration //
class WebAppTests {
// class body...
}
要覆盖默认值,可以使用隐式值属性指定其他基础资源路径。classpath:
和file:
资源前缀均受支持。如果未提供资源前缀,则假定该路径是文件系统资源。以下示例显示如何指定类路径资源:
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") //1
class WebAppTests {
// class body...
}
- 指定类路径资源。
注意,@WebAppConfiguration
必须与@ContextConfiguration
一起使用,可以在单个测试类中使用,也可以在测试类层次结构中使用。
有关更多详细信息,请参见@WebAppConfiguration javadoc。
参考代码:
org.liyong.test.annotation.test.spring.WebAppTests
@ContextHierarchy
@ContextHierarchy
是一个类级注解,用于定义集成测试的ApplicationContext
实例的层次结构。 @ContextHierarchy
应该用一个或多个@ContextConfiguration
实例的列表声明,每个实例定义上下文层次结构中的一个级别。以下示例演示了在单个测试类中使用@ContextHierarchy
(也可以在测试类层次结构中使用@ContextHierarchy
):
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
// class body...
}
参考代码:
org.liyong.test.annotation.test.spring.ContextHierarchyTests
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
// class body...
}
参考代码:
org.liyong.test.annotation.test.spring.WebIntegrationTests
如果需要合并或覆盖测试类层次结构中上下文层次结构的给定级别的配置,则必须通过在类层次结构的每个对应级别上为@ContextConfiguration
中的name属性提供相同的值来显式地命名该级别。有关更多示例,请参见上下文层次结构和@ContextHierarchy
javadoc。
@ActiveProfiles
@ActiveProfiles
是一个类级别的注解,用于声明在为集成测时加载ApplicationContext
时应启用哪些bean定义配置文件。
以下示例表明dev
配置文件应处于活动状态:
@ContextConfiguration
@ActiveProfiles("dev") //1
class DeveloperTests {
// class body...
}
- 指示开发配置文件应处于活动状态。
以下示例表明dev
和integration
配置文件均应处于活动状态:
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) //1
class DeveloperIntegrationTests {
// class body...
}
- 指示
dev
和integration
配置文件应该处于活动状态。
@ActiveProfiles
提供了对继承默认情况下超类声明的活动bean定义配置文件的支持。你还可以通过实现自定义ActiveProfilesResolver
并使用@ActiveProfiles
的resolver
属性对其进行注册,以编程方式解析活动bean定义配置文件。
参见环境配置文件和@ActiveProfiles javadoc的上下文配置以获得示例和更多细节。
参考代码:
org.liyong.test.annotation.test.spring.DeveloperIntegrationTests
@TestPropertySource
@TestPropertySource
是类级别的注解,可用于配置属性文件和内联属性的位置,这些属性和内联属性将被添加到环境中针对集成测试加载的ApplicationContext
的PropertySources
集中。
下面的示例演示如何从类路径声明属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties") //1
class MyIntegrationTests {
// class body...
}
- 从类路径根目录中的test.properties获取属性。
下面的示例演示如何声明内联属性:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) //1
class MyIntegrationTests {
// class body...
}
- 声明时区和端口属性。
有关示例和更多详细信息,请参见使用测试属性源进行上下文配置。
@DynamicPropertySource
@DynamicPropertySource
是方法级别的注解,可用于注册动态属性,以将动态属性添加到环境中针对集成测试加载的ApplicationContext
的PropertySources
集中。当你不预先知道属性的值时,例如,如果属性是由外部资源管理的,例如由Testcontainers
项目管理的容器,则动态属性很有用。
下面的示例演示如何注册动态属性:
@ContextConfiguration
class MyIntegrationTests {
static MyExternalServer server = // ...
@DynamicPropertySource //1
static void dynamicProperties(DynamicPropertyRegistry registry) { //2
registry.add("server.port", server::getPort); //3
}
// tests ...
}
- 使用
@DynamicPropertySource
注解静态方法。 - 接受
DynamicPropertyRegistry
作为参数。 - 注册要从服务器延迟检索的动态
server.port
属性。
有关更多详细信息,请参见使用动态属性源进行上下文配置。
@DirtiesContext
@DirtiesContext
表示底层的Spring ApplicationContext
在执行测试期间已被清理(即,该测试以某种方式修改或破坏了它(例如,通过更改单例bean的状态)),应将其关闭。当应用程序上下文被标记为清理时,它将从测试框架的缓存中删除并关闭。因此,对于需要具有相同配置元数据的上下文的后续测试,将重新构建底层Spring容器。
你可以将@DirtiesContext
用作同一类或类层次结构中的类级别和方法级别的注解。在这种情况下,取决于配置的methodMode
和classMode
,在任何此类带注解的方法之前或之后以及当前测试类之前或之后,ApplicationContext
均标记为清理。
以下示例说明了在各种配置情况下何时清理上下文:
-
在当前测试类之前,在类模式设置为
BEFORE_CLASS
的类上声明时。@DirtiesContext(classMode = BEFORE_CLASS) //1 class FreshContextTests { // some tests that require a new Spring container }
- 在当前测试类之前清理上下文。
-
在当前测试类之后,当在类模式设置为
AFTER_CLASS
(即默认类模式)的类上声明时。@DirtiesContext class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
- 当前测试类之后清理上下文。
-
在当前测试类中的每个测试方法之后,在类模式设置为
AFTER_EACH_TEST_METHOD
的类上声明时。@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
- 每种测试方法后清理上下文。
-
在当前测试之前,当在方法模式设置为
BEFORE_METHOD
的方法上声明时。@DirtiesContext(methodMode = BEFORE_METHOD) //1 @Test void testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container }
- 在当前测试方法之前清理上下文。
-
当前测试之后,当在方法模式设置为
AFTER_METHOD
的方法上声明时(即默认方法模式)。@DirtiesContext //1 @Test void testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied }
- 当前测试方法后清理上下文。
如果在使用@ContextHierarchy
将上下文配置为上下文层次结构的一部分的测试中使用@DirtiesContext
,则可以使用hierarchyMode
标志控制清除上下文缓存的方式。默认情况下,使用穷举算法清除上下文缓存,不仅包括当前级别,还包括共享当前测试共有的祖先上下文的所有其他上下文层次结构。驻留在公共祖先上下文的子层次结构中的所有ApplicationContext
实例都将从上下文缓存中删除并关闭。如果穷举算法对于特定用例来说过于强大,那么你可以指定更简单的当前级别算法,如下面的示例所示。
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class BaseTests {
// class body...
}
class ExtendedTests extends BaseTests {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) //1
void test() {
// some logic that results in the child context being dirtied
}
}
- 使用当前级别的算法。
有关EXHAUSTIVE
和CURRENT_LEVEL
算法的更多详细信息,请参见DirtiesContext.HierarchyMode javadoc。
@TestExecutionListeners
@TestExecutionListeners
定义了用于配置应在TestContextManager
中注册的TestExecutionListener
实现的类级元数据。通常,@TestExecutionListeners
与@ContextConfiguration
结合使用。
下面的示例演示如何注册两个TestExecutionListener
实现:
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) //1
class CustomTestExecutionListenerTests {
// class body...
}
- 注册两个
TestExecutionListener
实现。
默认情况下,@TestExecutionListeners
支持继承的监听器。有关示例和更多详细信息,请参见javadoc。
@Commit
@Commit
表示应在测试方法完成后提交用于事务性测试方法的事务。你可以将@Commit
用作@Rollback(false)
的直接替代品,以更明确地传达代码的意图。与@Rollback
类似,@ Commit
也可以声明为类级别或方法级别的注解。
以下示例显示了如何使用@Commit
注解:
@Commit //1
@Test
void testProcessWithoutRollback() {
// ...
}
- 将测试结果提交到数据库。
@Rollback
@Rollback
表示在测试方法完成后是否应回退用于事务性测试方法的事务。如果为true,则回滚该事务。否则,将提交事务(另请参见@Commit)。即使未明确声明@Rollback
,Spring TestContext
框架中用于集成测试的回滚默认为true。
当声明为类级注解时,@Rollback
定义测试类层次结构中所有测试方法的默认回滚语义。当声明为方法级别的注解时,@Rollback
定义特定测试方法的回滚语义,从而可能覆盖类级别的@Rollback
或@Commit
语义。
以下示例使测试方法的结果不回滚(即,结果已提交到数据库):
@Rollback(false) //1
@Test
void testProcessWithoutRollback() {
// ...
}
- 不要回滚结果。
@BeforeTransaction
@BeforeTransaction
表示,对于已配置为使用Spring的@Transactional
注解在事务内运行的测试方法,带注解的void方法应在事务开始之前运行。@BeforeTransaction
方法不需要public访问限定,可以在基于Java 8的接口默认方法。
以下示例显示了如何使用@BeforeTransaction
注解:
@BeforeTransaction //1
void beforeTransaction() {
// logic to be executed before a transaction is started
}
- 在事务之前运行此方法。
@AfterTransaction
@AfterTransaction
表示,对于已配置为通过使用Spring的@Transactional
注解在事务内运行的测试方法,带注解的void方法应在事务结束后运行。@AfterTransaction
方法不需要public访问限定,可以在基于Java 8的接口默认方法中声明。
@AfterTransaction //1
void afterTransaction() {
// logic to be executed after a transaction has ended
}
- 事务后运行此方法。
@Sql
@Sql
用于注解测试类或测试方法,以配置在集成测试期间针对给定数据库运行的SQL脚本。以下示例显示了如何使用它:
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})//1
void userTest() {
// execute code that relies on the test schema and test data
}
- 运行此测试的两个脚本。
有关更多详细信息,请参见使用@Sql声明式执行SQL脚本。
@SqlConfig
@SqlConfig
定义元数据,该元数据用于确定如何解析和运行使用@Sql
注解配置的SQL脚本。以下示例显示了如何使用它:
@Test
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "`", separator = "@@") //
)
void userTest() {
// execute code that relies on the test data
}
- 在SQL脚本中设置注释前缀和分隔符。
@SqlMergeMode
@SqlMergeMode
用于注释测试类或测试方法,以配置是否将方法级@Sql
声明与类级@Sql
声明合并。如果未在测试类或测试方法上声明@SqlMergeMode
,则默认情况下将使用OVERRIDE
合并模式。在OVERRIDE
模式下,方法级别的@Sql
声明将有效地覆盖类级别的@Sql
声明。
请注意,方法级别的@SqlMergeMode
声明将覆盖类级别的声明。
下面的示例演示如何在类级别使用@SqlMergeMode
。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) //1
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile() {
// execute code that relies on test data set 001
}
}
- 对于类中的所有测试方法,将
@Sql
合并模式设置为MERGE
。
下面的示例演示如何在方法级别使用@SqlMergeMode
。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) //1
void standardUserProfile() {
// execute code that relies on test data set 001
}
}
对于特定的测试方法,将@Sql
合并模式设置为MERGE
。
@SqlGroup
@SqlGroup
是一个容器注解,它聚合了多个@Sql
注解。你可以本地使用@SqlGroup
声明多个嵌套的@Sql
注解,也可以将其与Java 8对可重复注解的支持结合使用,其中@Sql
可以在同一类或方法上多次声明,从而隐式生成此容器注解。下面的示例显示如何声明一个SQL组:
@Test
@SqlGroup({ //1
@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脚本。
3.4.2 标准注解支持
Spring TestContext
框架的所有配置的标准语义都支持以下注解。请注意,这些注解并非特定于测试,可以在Spring 框架中的任何地方使用。
@Autowired
@Qualifier
@Value
-
@Resource
(javax.annotation
) 如果支持JSR-250 -
@ManagedBean
(javax.annotation
) 如果支持 JSR-250 -
@Inject
(javax.inject
) 如果支持 JSR-330 -
@Named
(javax.inject
) 如果支持 JSR-330 -
@PersistenceContext
(javax.persistence
) 如果支持JPA -
@PersistenceUnit
(javax.persistence
) 如果支持JPA @Required
-
@Transactional
(org.springframework.transaction.annotation
) with limited attribute support
JSR-250生命周期注解
在Spring
TestContext
框架中,可以在ApplicationContext
中配置的任何应用程序组件上使用具有标准语义的@PostConstruct
和@PreDestroy
。但是,这些生命周期注解在实际测试类中的使用受到限制。如果测试类中的方法使用
@PostConstruct
进行注解,则该方法将在基础测试框架的before
方法之前运行(例如,使用JUnit Jupiter的@BeforeEach
注解的方法),并且该方法适用于测试类中的每个测试方法。另一方面,如果测试类中的方法使用@PreDestroy
注解,则该方法将永远不会运行。因此,在测试类中,建议你使用来自基础测试框架的测试生命周期回调,而不是@PostConstruct
和@PreDestroy
。
3.4.3 Spring JUnit4测试注解
以下注解仅在与SpringRunner、Spring的JUnit 4规则或Spring的JUnit 4支持类一起使用时才受支持:
@IfProfileValue
@IfProfileValue
表示已为特定测试环境启用带注解的测试。如果配置的ProfileValueSource
返回提供的名称的匹配值,则使用测试。否则,测试将被禁用,并且实际上将被忽略。
你可以在类级别、方法级别或两者上应用@IfProfileValue
。对于该类或其子类中的任何方法,@IfProfileValue
的类级别用法优先于方法级别用法。具体来说,如果在类级别和方法级别都启用了测试,则启用该测试。缺少@IfProfileValue
意味着隐式启用了测试。这类似于JUnit 4的@Ignore
注解的语义,不同之处在于@Ignore
的存在始终会禁用测试。
以下示例显示了具有@IfProfileValue
注解的测试:
@IfProfileValue(name="java.vendor", value="Oracle Corporation") //1
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
- 仅当Java供应商是“ Oracle Corporation”时才运行此测试。
另外,你可以为@IfProfileValue
配置值列表(具有OR
语义)以在JUnit 4环境中实现类似于TestNG的测试组支持。考虑以下示例:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) //1
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
- 对单元测试和集成测试运行此测试。
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration
是一个类级别的注解,它指定检索通过@IfProfileValue
注解配置的配置文件值时要使用哪种ProfileValueSource
类型。如果未为测试声明@ProfileValueSourceConfiguration
,则默认使用SystemProfileValueSource
。以下示例显示了如何使用@ProfileValueSourceConfiguration
:
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) //1
public class CustomProfileValueSourceTests {
// class body...
}
- 使用自定义配置文件值源。
参考代码:
org.liyong.test.annotation.test.spring.ProfileValueTest
@Timed
@Timed
表示带注解的测试方法必须在指定的时间段(以毫秒为单位)内完成执行。如果单元测试片段执行时间超过指定的时间段,则测试将失败。
该时间段包括运行测试方法本身,测试的任何重复(请参见@Repeat
)以及测试套件的任何设置或拆除。以下示例显示了如何使用它:
@Timed(millis = 1000)//1
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to execute
}
- 将测试时间设置为一秒。
Spring的@Timed
注解与JUnit 4的@Test(timeout = ...)
支持具有不同的语义。具体来说,由于JUnit 4处理测试执行超时的方式(即通过在单独的线程中执行测试方法),如果测试花费的时间太长,@Test(timeout = ...)
会抢先通过测试。另一方面,Spring的@Timed
不会抢先通过测试,而是在失败之前等待测试完成。
@Repeat
@Repeat
表示必须重复运行带注解的测试方法。注解中指定了要执行测试方法的次数。重复执行的范围包括测试方法本身的执行以及测试套件中任何安装或拆除。以下示例显示了如何使用@Repeat
注解:
@Repeat(10) //1
@Test
public void testProcessRepeatedly() {
// ...
}
- 重复此测试十次。
3.4.4 Spring JUnit Jupiter测试注解
以下注解仅在与SpringExtension
和JUnit Jupiter
(即JUnit 5中的编程模型)结合使用时才受支持:
@SpringJUnitConfig
@SpringJUnitConfig
是一个组合注解,它将JUnit Jupiter
中的@ExtendWith(SpringExtension.class)
与Spring TestContext
框架中的@ContextConfiguration
组合在一起。它可以在类级别用作@ContextConfiguration
的直接替代。关于配置选项,@ContextConfiguration
和@SpringJUnitConfig
之间的唯一区别是可以使用@SpringJUnitConfig
中的value属性声明组件类。
以下示例显示如何使用@SpringJUnitConfig
注解指定配置类:
@SpringJUnitConfig(TestConfig.class) //1
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
- 指定配置类。
以下示例显示如何使用@SpringJUnitConfig
注解指定配置文件的位置:
@SpringJUnitConfig(locations = "/test-config.xml") //1
class XmlJUnitJupiterSpringTests {
// class body...
}
- 指定配置文件的位置。
有关更多详细信息,请参见上下文管理以及@SpringJUnitConfig
和@ContextConfiguration
的javadoc。
@SpringJUnitWebConfig
@SpringJUnitWebConfig
是一个组合的注解,它将来自JUnit Jupiter
的@ExtendWith(SpringExtension.class)
与来自Spring TestContext
框架的@ContextConfiguration
和@WebAppConfiguration
组合在一起。你可以在类级别使用它作为@ContextConfiguration
和@WebAppConfiguration
的直接替代。关于配置选项,@ ContextConfiguration
和@SpringJUnitWebConfig
之间的唯一区别是可以使用@SpringJUnitWebConfig
中的value属性来声明组件类。另外,只能使用@SpringJUnitWebConfig
中的resourcePath
属性来覆盖@WebAppConfiguration
中的value
属性。
以下示例显示如何使用@SpringJUnitWebConfig
注解指定配置类:
@SpringJUnitWebConfig(TestConfig.class) //1
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
- 指定配置类。
以下示例显示如何使用@SpringJUnitWebConfig注解指定配置文件的位置:
@SpringJUnitWebConfig(locations = "/test-config.xml") //1
class XmlJUnitJupiterSpringWebTests {
// class body...
}
- 指定配置文件的位置。
有关更多详细信息,请参见上下文管理以及@SpringJUnitWebConfig,@ContextConfiguration和@WebAppConfiguration的javadoc。
参考代码:
org.liyong.test.annotation.test.spring.ConfigurationClassJUnitJupiterSpringWebTests
@TestConstructor
@TestConstructor
是类型级别的注解,用于配置如何从测试的ApplicationContext
中的组件自动连接测试类构造函数的参数。
如果在测试类上不存在@TestConstructor
或meta-present
,则将使用默认的测试构造函数自动装配模式。有关如何更改默认模式的详细信息,请参见下面的提示。但是请注意,构造函数上的@Autowired
本地声明优先于@TestConstructor
和默认模式。
更改默认的测试构造函数自动装配模式
可以通过将JVM系统属性
spring.test.constructor.autowire.mode
设置为all来更改默认的测试构造函数自动装配模式。或者,可以通过SpringProperties
机制更改默认模式。如果未设置
spring.test.constructor.autowire.mode
属性,则不会自动装配测试类构造函数。从Spring框架5.2开始,仅将
@TestConstructor
与SpringExtension
结合使用以与JUnit Jupiter一起使用。请注意,SpringExtension
通常会自动为你注册-例如,在使用@SpringJUnitConfig
和@SpringJUnitWebConfig
之类的注解或Spring Boot Test中与测试相关的各种注解时。
@EnabledIf
@EnabledIf
用于表示已注解的JUnit Jupiter测试类或测试方法启用,如果提供的表达式的值为true
,则应运行@EnabledIf
。具体来说,如果表达式的计算结果为Boolean.TRUE
或等于true
的字符串(忽略大小写),则启用测试。在类级别应用时,默认情况下也会自动启用该类中的所有测试方法。
表达式可以是以下任意一种:
- Spring表达式语言。例如:
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
- Spring
Environment
中可用属性的占位符。例如:@EnabledIf("${smoke.tests.enabled}")
- 文本文字。例如:
@EnabledIf("true")
但是请注意,不是属性占位符的动态解析结果的文本文字的实际值为零,因为@EnabledIf(“ false”)
等效于@Disabled
,而@EnabledIf(“ true”)
在逻辑上是没有意义的。
你可以使用@EnabledIf
作为元注解来创建自定义的组合注释。例如,你可以创建一个自定义@EnabledOnMac
注解,如下所示:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
@DisabledIf
@DisabledIf
用于表示已注解的JUnit Jupiter测试类或测试方法被禁用,并且如果提供的表达式的值为true
,则不应执行该操作。具体来说,如果表达式的计算结果为Boolean.TRUE
或等于true的字符串(忽略大小写),则测试将被禁用。当在类级别应用时,该类中的所有测试方法也会自动禁止。
表达式可以是以下任意一种:
- Spring表达式语言。例如:
@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
- Spring Environment中可用属性的占位符。例如:
@DisabledIf("${smoke.tests.disabled}")
- 文本文字:例如:
@DisabledIf("true")
但是请注意,不是属性占位符的动态解析结果的文本文字的实际值为零,因为@DisabledIf(“ true”
)等效于@Disabled
,而@DisabledIf(“ false”)
在逻辑上是没有意义的。
你可以将@DisabledIf
用作元注释,以创建自定义的组合注解。例如,你可以创建一个自定义@DisabledOnMac
注解,如下所示:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
3.4.5 测试的元注解支持
你可以将大多数与测试相关的注解用作元注解,以创建自定义的组合注解,并减少整个测试套件中的重复配置。
你可以将以下各项用作与TestContext框架结合使用的元注解。
@BootstrapWith
@ContextConfiguration
@ContextHierarchy
@ActiveProfiles
@TestPropertySource
@DirtiesContext
@WebAppConfiguration
@TestExecutionListeners
@Transactional
@BeforeTransaction
@AfterTransaction
@Commit
@Rollback
@Sql
@SqlConfig
@SqlMergeMode
@SqlGroup
-
@Repeat
(仅支持 JUnit 4) -
@Timed
(仅支持 JUnit 4) -
@IfProfileValue
(仅支持 JUnit 4) -
@ProfileValueSourceConfiguration
(仅支持 JUnit 4) -
@SpringJUnitConfig
(仅支持 JUnit Jupiter) -
@SpringJUnitWebConfig
(仅支持JUnit Jupiter) -
@TestConstructor
(仅支持 JUnit Jupiter) -
@EnabledIf
(仅支持 JUnit Jupiter) -
@DisabledIf
(仅支持 JUnit Jupiter)
考虑以下示例:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
如果发现我们要在基于JUnit 4的测试套件中重复上述配置,则可以通过引入一个自定义的组合注解来减少重复,该注解集中了Spring的通用测试配置,如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
然后,我们可以使用我们的自定义@TransactionalDevTestConfig
的注解来简化单个基于JUnit 4的测试类的配置,如下所示:
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
如果我们编写使用JUnit Jupiter的测试,则可以进一步减少代码重复,因为JUnit 5中的注解也可以用作元注解。考虑以下示例:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
如果发现我们在基于JUnit Jupiter的测试套件中重复了前面的配置,则可以通过引入一个自定义组合注解来减少重复,该注解集中了Spring和JUnit Jupiter的通用测试配置,如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
然后,我们可以使用我们的自定义@TransactionalDevTestConfig
的注解来简化基于单个JUnit Jupiter的测试类的配置,如下所示:
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
由于JUnit Jupiter支持使用@Test
、@RepeatedTest
、ParameterizedTest
和其他作为元注解,因此你也可以在测试方法级别创建自定义的组合注解。例如,如果我们希望创建一个组合的注解,将JUnit Jupiter的@Test
和@Tag
注解与Spring的@Transactional
注解相结合,则可以创建一个@TransactionalIntegrationTest
注解,如下所示:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
然后,我们可以使用自定义的@TransactionalIntegrationTest
注解来简化基于单个JUnit Jupiter的测试方法的配置,如下所示:
@TransactionalIntegrationTest
void saveOrder() { }
@TransactionalIntegrationTest
void deleteOrder() { }
有关更多详细信息,请参见Spring Annotation编程模型Wiki页。
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公众号:
技术交流群: