Spring Testing
# 测试
5.3.2
本章介绍了Spring对集成测试的支持和单元测试的最佳实践。Spring团队倡导测试驱动的开发(TDD)。Spring团队发现,正确使用控制反转(IOC)确实使单元和集成测试更加容易(因为存在setter 方法和类上适当的构造函数,因此无需设置服务定位器注册表和类似结构就能很容的把它们联系在一起)。
# 1. 介绍Spring Testing
测试是企业软件开发的组成部分。本章重点介绍了IOC原理为单元测试增添的价值以及Spring 框架对集成测试支持的好处。(企业中对测试的=处理超出了本参考手册的范围。)
# 2. 单元测试
依赖注入应降低代码对容器的依赖,而不是像传统的Java EE开发那样。构成您应用程序的POJOs应该在JUnit 或TestNG 测试中可进行测试,并通过使用new操作符,不需要使用Spring或任何其他容器来实例化对象。您可以使用 mock objects (opens new window)(与其他有价值的测试技术结合使用)来隔离测试代码。如果您遵循有关Spring的体系结构建议,则代码库的清洁分层和组件促进了更容易的单元测试。例如,您可以通过stub或mock DAO或仓储层接口来测试服务层对象,而无需在运行单元测试时访问持久数据。
真正的单元测试通常非常快地运行,因为没有运行时基础架构可以设置。真正的单位测试作为开发方法的一部分可以提高您的生产效率。您可能不需要测试章节的这一部分来帮助您为基于IOC的应用程序编写有效的单元测试。但是,对于某些单元测试方案,Spring框架提供了mock对象和测试支持类,这将在本章中进行描述。
# 2.1. Mock对象
Spring包括许多专门用于mock的软件包:
- Environment (opens new window)
- JNDI (opens new window)
- Servlet API (opens new window)
- Spring Web Reactive (opens new window)
# 2.1.1. Environment
org.springframework.mock.env软件包包含Environment和PropertySource抽象的模拟实现(请参阅Bean Definition Profiles (opens new window)和PropertySource Abstraction (opens new window))。MockEnvironment和模拟MockPropertySource对于开发容器外的依赖于特定环境属性代码的测试很有用。
# 2.1.2. JNDI
org.springframework.mock.jndi软件包包含JNDI SPI的部分实现,您可以使用它来为测试套件或独立应用程序设置简单的JNDI环境。例如,如果JDBC DataSource实例与Java EE容器中的测试代码绑定到相同的JNDI名称,则可以在未经修改的情况下重复使用应用程序代码和配置。
·⚠️org.springframework.mock.jndi软件包中的模拟JNDI支持正式在Spring 5.2正式弃用,因为其已支持Simple-JNDI (opens new window)等第三方的完整解决方案。
# 2.1.3. Servlet API
org.springframework.mock.web软件包包含一组全面的Servlet API模拟对象,这些对象可用于测试Web上下文,控制器和过滤器。这些模拟对象针对的是使用Spring Web MVC框架的应用,且通常比动态模拟对象(例如EasyMock (opens new window))或可替代的Servlet API模拟对象(例如MockObjects (opens new window))更方便使用。
💡从Spring 5.0起,
org.springframework.mock.web中的模拟对象基于Servlet 4.0 API。
Spring MVC测试框架在模拟Servlet API对象上构建,为Spring MVC提供集成测试框架。请参阅MockMvc (opens new window)。
# 2.1.4. Spring Web Reactive
org.springframework.mock.http.server.reactive软件包包含ServerHttpRequest和ServerHttpRequest的模拟实现,可用于WebFlux应用程序。org.springframework.mock.web.server软件包包含一个取决于模拟请求和响应对象的模拟ServerWebExchange。
MockServerHttpRequest和MockServerHttpResponse都继承服务器特定实现的抽象基类,并与他们共享行为。例如,一旦创建了一个不可变的模拟请求,但是您可以使用ServerHttpRequest的mutate()方法来创建可修改的实例。
为了使模拟响应正确实现写入归约并返回写入完成句柄(即Mono<Void>),默认情况下,它使用Flux的cache().then(),缓冲数据并使其在测试中可用于断言。应用程序可以设置自定义写入功能(例如,测试无限流)。
WebTestClient (opens new window)在模拟请求和响应上构建,以提供没有HTTP服务器的WebFlux应用程序的支持。客户也可以使用运行服务器用于端到端测试。
# 2.2. 单元测试支持类
Spring包括许多可以帮助单元测试的类。他们分为两类:
# 2.2.1. 一般测试工具类
org.springframework.test.util软件包包含多个通用工具方法,用于单元和集成测试。
ReflectionTestUtils是基于反射的工具方法的集合。您可以在测试场景中使用这些方法,在这些场景中,您需要更改常数的值,设置非public字段,调用非public setter方法,或调用非public配置或生命周期回调方法,例如以下内容:
- ORM 框架 (例如JPA和 Hibernate) condone private或protected的字段访问,而不是领域实体中属性的public setter方法。
- Spring对注解的支持(例如
@Autowired,@Inject和@Resource),可为private或protected字段,setter方法和配置方法提供依赖注入。 - 使用
@PostConstruct和@PreDestroy等注解用于生命周期回调方法。
AopTestUtils (opens new window)是与AOP相关的工具方法的集合。您可以使用这些方法来获取隐藏在一个或多个Spring代理后面的基础目标对象的引用。例如,如果您通过使用EasyMock或Mockito之类的库将bean配置为动态mock,并且该模拟包裹在Spring 代理中,则可能需要直接访问基础模拟以对其进行配置并执行验证。有关Spring的核心AOP工具方法,请参阅AopUtils (opens new window)和AopProxyUtils (opens new window)。
# 2.2.2. Spring MVC 测试工具类
org.springframework.test.web软件包包含ModelAndViewAssert (opens new window),您可以将其与JUnit,TestNG或任何其他测试框架结合使用,用于与Spring MVC ModelAndView对象进行的单元测试。
💡单元测试Spring MVC Controllers
要将Spring MVC Controller类视为POJOs进行单元测试,请将ModelAndViewAssert与Spring的Servlet API mocks (opens new window)中的MockHttpServletRequest,MockHttpSession等等结合使用。要用Spring MVC的WebApplicationContext配置结合使用Spring MVC和REST Controller类进行集成测试,请改用 Spring MVC Test Framework (opens new window)。
# 3. 集成测试
本章(本章的其余大部分)涵盖了spring应用程序的集成测试。它包括以下主题:
- Overview (opens new window)
- Goals of Integration Testing (opens new window)
- JDBC Testing Support (opens new window)
- Annotations (opens new window)
- Spring TestContext Framework (opens new window)
- MockMvc (opens new window)
# 3.1. 概述
无需部署到应用程序服务器或连接到其他企业基础架构就能够执行一些集成测试很重要。这样做可以让您测试以下内容:
- Spring IoC 容器上下文的正确装配
- 使用JDBC或ORM工具访问数据。这可以包括SQL语句,Hibernate 查询,JPA实体映射等。
Spring框架在spring-test模块中提供了一流的支持。实际的JAR文件的名称可能包括发行版本,也可能是org.springframework.test形式,具体取决于您从何处获得的(请参阅 section on Dependency Management (opens new window))。
该库包括org.springframework.test软件包,其中包含用于与Spring容器集成测试的有价值的类。该测试不依赖应用程序服务器或其他部署环境。此类测试的运行速度比单元测试要慢,但是比依靠部署到应用程序服务器的测试或远程测试要快得多。
单位和集成测试支持以注解驱动的 Spring TestContext Framework (opens new window)的形式提供。TestContext框架对实际使用测试框架不可知,该框架允许在包括JUnit,TestNG等各种环境中的测试工具。
# 3.2. 集成测试的目标
Spring的集成测试支持以下主要目标:
- 管理测试间的Spring IoC container caching (opens new window)
- 提供 Dependency Injection of test fixture instances (opens new window).
- 提供适合集成测试的 transaction management (opens new window)
- 提供 Spring-specific base classes (opens new window) ,以帮助开发人员编写集成测试。
# 3.2.1. 上下文管理和缓存
Spring TestContext框架提供了Spring ApplicationContext 实例和WebApplicationContext 实例的一致加载以及这些上下文的缓存。支持加载上下文的缓存非常重要,因为启动时间可能会成为一个问题 - 不是因为Spring本身的开销,而是因为Spring容器实例化的对象需要时间来实例化。例如,一个具有50至100个Hibernate 映射文件的项目可能需要10到20秒才能加载映射文件,在每个测试中承担这样的开销导致整体测试运行较慢,从而降低了开发人员的生产效率。
测试类通常会声明XML格式的一系列资源位置或Groovy配置元数据(通常在类路径中)或用于配置应用程序的组件类数组。这些位置或类与web.xmll中指定的或其他配置文件相同或相似。
默认情况下,加载后,为每个测试重复使用了配置的ApplicationContext。因此,每个测试套件仅产生一次设置成本,随后的测试执行速度要快得多。在这种情况下,“测试套件”一词是指在同一JVM中运行的所有测试--例如,所有测试都来自给定项目或模块的 Ant, Maven, 或Gradle 构建。在不太可能的情况下,测试破坏了应用程序上下文并需要重新加载(例如,通过修改bean定义或应用程序对象的状态)可以配置TestContext 框架以重新加载配置并在执行下一个应用程序之前重建应用程序上下文测试。
请参阅 TestContext框架的Context Management (opens new window)和 Context Caching (opens new window)。
# 3.2.2. 测试的依赖注入
当TestContext 框架加载您的应用程序上下文时,它可以选择使用依赖项注入来配置测试类的实例。这提供了一种方便的机制,可通过使用应用程序上下文预配置的Bean来设置测试。这里的一个很大的好处是,您可以在各种测试场景中重复使用应用程序上下文(例如,用于配置Spring管理对象图,事务代理,数据源实例等),从而避免需要复制复杂的测试固定设置以进行单个测试案例。
例如,考虑一个场景,其中我们有一个类(HibernateTitleRepository),该类实现了Title域实体的数据访问逻辑。我们想编写测试以下领域的集成测试:
- Spring配置:基本上,所有内容是否与HibernateTitleRepository Bean的配置有关?
- Hibernate 映射文件配置:所有内容是否正确映射,并且是否适当地进行懒加载设置?
- HibernateTitleRepository的逻辑:该类配置的实例是否按预期执行?
请参阅使用TestContext framework (opens new window)的测试的依赖注入。
# 3.2.3. 事务管理
在访问真实数据库的测试中,一个常见问题是它们对持久存储状态的影响。即使您使用开发数据库,对状态的更改也可能影响未来的测试。同样,在事务之外,无法执行(或验证)许多操作(例如插入或修改持久数据)。
TestContext框架解决了此问题。默认情况下,该框架为每个测试创建并回滚事务。您编写代码时可以假定事务存在。如果您在测试中调用事务代理对象,则根据配置的事务语义,它们能正确工作。
另外,如果测试方法在用于测试的事务中运行时删除了所选表的内容,则默认情况下的事务将回滚,并且数据库在执行测试之前返回其状态。通过使用测试应用程序上下文中定义的PlatformTransactionManager bean,向测试提供了事务支持。
如果您想进行事务提交(不寻常,但当您希望特定测试填充或修改数据库时偶尔有用),可以告诉TestContext框架通过使用@Commit (opens new window) 使事务提交,而不是回滚。
请参阅TestContext framework (opens new window)的事务管理。
# 3.2.4. 集成测试支持类
Spring TestContext框架提供了几个抽象支持类,以简化集成测试的编写。这些基本测试类提供了明确定义的钩子,并在测试框架中提供了方便的实例变量和方法,可让您访问:
ApplicationContext,用于执行明确的Bean查找或测试整个上下文的状态。JdbcTemplate,用于执行SQL语句查询数据库。您可以使用此类查询在执行与数据库相关的应用程序代码之前和之后确认数据库状态,并确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具一起使用时,请务必避免 false positives (opens new window)。
此外,您可能需要创建自定义的,应用程序范围的超类,并使用实例变量和特定于项目的方法。
请参阅TestContext framework (opens new window)的支持类。
# 3.3. JDBC测试支持
org.springframework.test.jdbc软件包包含JdbcTestUtils,它是JDBC相关的工具函数的集合,旨在简化标准数据库测试方案。具体而言,JdbcTestUtils提供了以下静态工具方法:
countRowsInTable(..): 计算给定表中的行数。countRowsInTableWhere(..): 使用提供的Where子句来计算给定表中的行数。deleteFromTables(..): 从指定的表中删除所有行。deleteFromTableWhere(..): 使用提供的Where子句从给定表中删除行。dropTables(..): drop指定的表。
💡
AbstractTransactionalJUnit4SpringContextTests(opens new window)和AbstractTransactionalTestNGSpringContextTests(opens new window)提供了便利的方法,可将上述方法委派给JdbcTestUtils中的方法。spring-jdbc模块提供了配置和启动嵌入式数据库的支持,您可以在与数据库交互的集成测试中使用。有关详细信息,请参阅Embedded Database Support (opens new window) 和 Testing Data Access Logic with an Embedded Database (opens new window)。
# 3.4. 注解
本节涵盖了测试Spring应用程序时可以使用的注解。它包括以下主题:
- Spring Testing Annotations (opens new window)
- Standard Annotation Support (opens new window)
- Spring JUnit 4 Testing Annotations (opens new window)
- Spring JUnit Jupiter Testing Annotations (opens new window)
- Meta-Annotation Support for Testing (opens new window)
# 3.4.1. Spring Testing注解
Spring框架提供了以下一组特定于Spring的注解,您可以在单元测试和集成测试中使用,并与TestContext 框架结合使用。有关更多信息,请参见相应的Javadoc,包括默认属性值,属性别名和其他详细信息。
Spring的测试注解包括以下内容:
@BootstrapWith(opens new window)@ContextConfiguration(opens new window)@WebAppConfiguration(opens new window)@ContextHierarchy(opens new window)@ActiveProfiles(opens new window)@TestPropertySource(opens new window)@DynamicPropertySource(opens new window)@DirtiesContext(opens new window)@TestExecutionListeners(opens new window)@Commit(opens new window)@Rollback(opens new window)@BeforeTransaction(opens new window)@AfterTransaction(opens new window)@Sql(opens new window)@SqlConfig(opens new window)@SqlMergeMode(opens new window)@SqlGroup(opens new window)
@BootstrapWith
@BootstrapWith是一个类级别注解,您可以用来配置Spring TestContext框架的自动化方式。具体来说,您可以使用@BootstrapWith指定自定义TestContextBootstrapper。有关更多详细信息,请参见bootstrapping the TestContext framework (opens new window) 。
@ContextConfiguration
@ContextConfiguration定义类别元数据,用于确定如何为集成测试加载和配置ApplicationContext。具体而言,@ContextConfiguration声明应用程序上下文资源位置或用于加载上下文的组件类。
资源位置通常是位于类路径中的XML配置文件或Groovy脚本,而组件类通常为@Configuration类。但是,资源位置还可以引用文件系统中的文件和脚本,并且组件类可以是@Component类,@Service类等。有关更多详细信息,请参见Component Classes (opens new window) 。
下面的示例显示了@ContextConfiguration引用XML文件:
@ContextConfiguration("/test-config.xml")
class XmlApplicationContextTests {
// class body...
}
以下示例显示了@ContextConfiguration引用类:
@ContextConfiguration(classes = TestConfig.class)
class ConfigClassApplicationContextTests {
// class body...
}
作为替代方案或声明资源位置或组件类,您还可以使用@ContextConfiguration来声明ApplicationContextInitializer类。以下示例显示了这样的情况:
@ContextConfiguration(initializers = CustomContextIntializer.class)
class ContextInitializerTests {
// class body...
}
您可以选择使用@ContextConfiguration来声明ContextLoader策略。但是,请注意,您通常不需要明确配置加载器,因为默认加载器支持initializers和locations或classes。
以下示例同时使用location 和loader:
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class)
class CustomLoaderXmlApplicationContextTests {
// class body...
}
ℹ️@ContextConfiguration为继承资源位置或配置类以及通过超类或包装(enclosing)类声明的上下文初始化器提供了支持。
有关更多详细信息,请参见Context Management (opens new window), @Nested test class configuration (opens new window) javadocs。
@WebAppConfiguration
@WebAppConfiguration是类级别注释,您可以用它来声明应用程序上下文应该是WebApplicationContext的集成测试。测试类中仅使用@WebAppConfiguration,就可确保使用"file:src/main/webapp"(即资源基本路径)的默认值为测试加载WebApplicationContext。该场景后的资源基础路径用来创建MockServletContext,以ServletContext角色为测试WebApplicationContext服务。
以下示例显示如何使用@WebAppConfiguration:
@ContextConfiguration
@WebAppConfiguration
class WebAppTests {
// class body...
}
要覆盖默认值,您可以使用隐式value属性来指定不同的基本资源路径。classpath:和file:前缀都支持。如果没有提供资源前缀,则假定该路径为文件系统资源。以下示例显示了如何指定类路径资源:
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources")
class WebAppTests {
// class body...
}
请注意,@WebAppConfiguration必须与@ContextConfiguration一起使用,无论是在单个测试类中还是在测试类层次结构中。有关更多详细信息,请参见@WebAppConfiguration (opens new window) javadoc。
@ContextHierarchy
@ContextHierarchy是一个类级别注解,用于定义集成测试的ApplicationContext 实例的层次结构。@ContextHierarchy应该和一个或多个@ContextConfiguration实例的列表一起声明,每个列表在上下文层次结构中定义了一个级别。以下示例展示了在单个测试类中使用@ContextHierarchy(@ContextHierarchy也可以在测试类层次结构中使用):
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
// class body...
}
如果您需要合并或覆盖测试类层次结构中为给定级别的上下文层次结构配置,则必须通过显示提供在每个类层次结构中的对应级别的@ContextConfigurationname的name属性来命名此级别。有关进一步的示例,请参见 Context Hierarchies (opens new window) 和@ContextHierarchy (opens new window) javadoc。
@ActiveProfiles
@ActiveProfiles是一个类级别注解,用于声明在加载ApplicationContext以进行集成测试时,使用哪个bean定义配置文件。
以下示例表明应激活dev配置文件:
@ContextConfiguration
@ActiveProfiles("dev")
class DeveloperTests {
// class body...
}
以下示例表明应激活dev和integration配置文件:
@ContextConfiguration
@ActiveProfiles({"dev", "integration"})
class DeveloperIntegrationTests {
// class body...
}
ℹ️@ActiveProfiles为继承超类和包装类声明的激活的bean定义配置文件提供了支持。您还可以通过实现自定义的
ActiveProfilesResolver(opens new window)并使用@ActiveProfiles的resolver属性来编程式解析激活的bean定义配置文件。
有关示例和更多详细信息,请参见Context Configuration with Environment Profiles (opens new window), @Nested test class configuration (opens new window)和 @ActiveProfiles (opens new window) javadoc。
@TestPropertySource
@TestPropertySource是一个类级别注解,您可以用来配置属性文件的位置和内联属性的位置,改内联属性会添加到集成测试中已加载的ApplicationContext的Environment中的PropertySources。
以下示例演示了如何从类路径声明属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties")
class MyIntegrationTests {
// class body...
}
下面的示例演示了如何声明内联属性:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
class MyIntegrationTests {
// class body...
}
有关示例和更多详细信息,请参见 Context Configuration with Test Property Sources (opens new window)。
@DynamicPropertySource
@DynamicPropertySource是一种方法级别注解,您可以使用它来注册动态属性,该动态属性会添加到集成测试中已加载的ApplicationContext的Environment中的PropertySources。当您不知道预期属性的值时,动态属性将很有用 - 例如,如果属性由外部资源管理,例如由Testcontainers (opens new window)项目管理的容器。
以下示例演示了如何注册动态属性:
@ContextConfiguration
class MyIntegrationTests {
static MyExternalServer server = // ...
@DynamicPropertySource
static void dynamicProperties(DynamicPropertyRegistry registry) {
registry.add("server.port", server::getPort); //从服务器懒加载
}
// tests ...
}
参考Context Configuration with Dynamic Property Sources (opens new window)以获取更多信息。
@DirtiesContext
@DirtiesContext表明,在执行测试期间,基础Spring ApplicationContext已被污染(即,测试以某种方式修改或损坏了上上下文,例如,通过更改单例bean的状态),应关闭它。当将应用程序上下文标记为污染时,将其从测试框架的缓存中删除并关闭。结果,对于需要具有相同配置元数据的上下文的任何后续测试,重建基础Spring容器。
您可以将@DirtiesContext用作同一类或类层次结构中的类和方法级注解。在这种情况下,根据配置的methodMode和classMode,ApplicationContext在任何注解方法以及当前测试类之前或之后都标记为污染。
以下示例说明了各种配置场景的上下文何时会污染:
- 在当前测试类之前,类模式设置为BEFORE_CLASS。
@DirtiesContext(classMode = BEFORE_CLASS) // Dirty the context before the current test class.
class FreshContextTests {
// some tests that require a new Spring container
}
- 在当前测试类之后,类模式设置为AFTER_CLASS(默认类模式)。
@DirtiesContext // Dirty the context after the current test class.
class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
- 在当前测试类中的每种测试方法之前,类模式设置为BEFORE_EACH_TEST_METHOD。
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // Dirty the context before each test method.
class FreshContextTests {
// some tests that require a new Spring container
}
- 在当前测试类中的每种测试方法之后,类模式设置为AFTER_EACH_TEST_METHOD。
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) //Dirty the context after each test method
class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
- 在当前测试之前,将方法模式设置为BEFORE_METHOD。
@DirtiesContext(methodMode = BEFORE_METHOD) //Dirty the context before the current test method.
@Test
void testProcessWhichRequiresFreshAppCtx() {
// some logic that requires a new Spring container
}
- 在当前测试之后,将方法模式设置为AFTER_METHOD(默认方法模式)。
@DirtiesContext //Dirty the context after the current test method.
@Test
void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
}
如果将@DirtiesContext用在上下文作为使用@ContextHierarchy的上下文层次结构的一部分的测试中,则可以使用hierarchyMode标志来控制上下文缓存的清除方式。默认情况下,详尽的算法用于清除上下文缓存,不仅包括当前级别,还包括所有其他上下文层次结构,它们共享当前测试共有的祖上下文。存在于共同祖先上下文的子层结构中的所有ApplicationContext实例都从上下文缓存中删除并关闭。如果详尽的算法不适用于特定用例,则可以指定更简单的当前级算法,如以下示例所示:
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class BaseTests {
// class body...
}
class ExtendedTests extends BaseTests {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL)
void test() {
// some logic that results in the child context being dirtied
}
}
有关EXHAUSTIVE和CURRENT_LEVEL算法的更多详细信息,请参见DirtiesContext.HierarchyMode (opens new window) javadoc。
@TestExecutionListeners
@TestExecutionListeners定义了用于配置应在TestContextManager注册的TestExecutionListener实现的类级元数据。通常,@TestExecutionListeners与@ContextConfiguration结合使用。
以下示例显示了如何注册两个TestExecutionListener实现:
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
class CustomTestExecutionListenerTests {
// class body...
}
默认情况下,@TestExecutionListeners为从超类或包装类继承的监听器提供支持。有关示例和更多详细信息,请参见 @Nested test class configuration (opens new window)和 @TestExecutionListeners javadoc (opens new window) 。
@Commit
@Commit表示在测试方法完成后应进行事务测试方法的提交。您可以将@Commit用作@Rollback(false)的直接替换,以更明确地传达代码的意图。类似于@Rollback,@Commit也可以声明为类级或方法级注解。
以下示例显示了如何使用@Commit:
@Commit
@Test
void testProcessWithoutRollback() {
// ...
}
@Rollback
@Rollback表明在测试方法完成后是否应将事务测试方法的事务回滚。如果是true,则事务将回滚。否则,事务将被提交。在Spring TestContext 框架中即使未明确声明@Rollback,进行集成测试的回滚默认为true。
当声明为类级注解时, @Rollback定义了测试类层次结构中所有测试方法的默认回滚语义。当声明为方法级注解时, @Rollback定义了特定测试方法的回滚语义,可能会覆盖类级别的@Rollback或@Commit语义。
下面的示例导致测试方法的结果不回滚(也就是说,结果已提交到数据库):
@Rollback(false)
@Test
void testProcessWithoutRollback() {
// ...
}
@BeforeTransaction
@BeforeTransaction表明应在启动事务之前运行带注解的void方法,对于已配置为通过使用Spring的 @Transactional在事务中运行的测试方法。@BeforeTransaction方法不必是public,并且可以在基于Java 8的接口默认方法上声明。
以下示例显示了如何使用@BeforeTransaction:
@BeforeTransaction
void beforeTransaction() {
// logic to be run before a transaction is started
}
@AfterTransaction
@AfterTransaction表明应在事务结束之后运行带注解的void方法,对于已配置为通过使用Spring的 @Transactional在事务中运行的测试方法。@AfterTransaction方法不必是public,并且可以在基于Java 8的接口默认方法上声明。
以下示例显示了如何使用@AfterTransaction:
@AfterTransaction
void afterTransaction() {
// logic to be run after a transaction has ended
}
@Sql
@Sql用于注解测试类或测试方法,以配置在集成测试期间针对给定数据库运行的SQL脚本。以下示例显示了如何使用它:
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// run code that relies on the test schema and test data
}
参考 Executing SQL scripts declaratively with @Sql (opens new window) 获取更多细节。
@SqlConfig
@SqlConfig定义元数据用于确定如何解析和运行通过@Sql配置的SQL脚本。以下示例显示了如何使用它:
@Test
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "`", separator = "@@")
)
void userTest() {
// run code that relies on the test data
}
@SqlMergeMode
@SqlMergeMode用于注解测试类或测试方法,以配置方法级别@Sql声明是否与类级别@Sql声明合并。如果未在测试类或测试方法上声明@SqlMergeMode,则默认情况下将使用OVERRIDE模式。通过OVERRIDE模式,方法级@Sql声明将有效地覆盖类级别的@Sql声明。
请注意,方法级@SqlMergeMode声明覆盖了类级声明。
以下示例显示了如何在类级别使用@SqlMergeMode:
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE)
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile() {
// run code that relies on test data set 001
}
}
以下示例显示了如何在方法级别使用@SqlMergeMode。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE)
void standardUserProfile() {
// run code that relies on test data set 001
}
}
@SqlGroup
@SqlGroup是一个容器注解,可以汇总几个@Sql注解。您可以本地使用@SqlGroup来声明几个嵌套@Sql注解,也可以与Java 8的可重复注解一起使用,其中可以在同一类或方法上多次声明@Sql,隐式地生成该容器注解。以下示例显示了如何声明SQL组:
@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
}
# 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 (opens new window)
ℹ️ JSR-250生命周期注解
在Spring TestContext框架中,您可以在ApplicationContext中配置的任何组件上使用@PostConstruct和@PreDestroy的标准语义。但是,这些生命周期注解在实际的测试类中的使用有限制。
如果用@PostConstruct注解测试类中的方法,则该方法在基础测试框架的任何方法之前运行(例如,用Junit Jupiter的@BeforeEach注释的方法),并且适用于测试类中的每种测试方法。另一方面,如果用@PreDestroy注解测试类中的方法,则该方法永远不会运行。因此,在测试类中,我们建议您使用基础测试框架的测试生命周期回调,而不是@PostConstruct和@PreDestroy。
# 3.4.3. Spring JUnit4测试注解
仅当与 SpringRunner (opens new window), Spring’s JUnit 4 rules (opens new window), 或Spring’s JUnit 4 support classes (opens new window)一起使用时,才支持以下注解:
@IfProfileValue(opens new window)@ProfileValueSourceConfiguration(opens new window)@Timed(opens new window)@Repeat(opens new window)
@IfProfileValue
@IfProfileValue表示针对特定的测试环境启用了被注解的测试。如果配置的ProfileValueSource返回提供的name的匹配value,则启用测试。否则,测试将被禁用并被忽略。
您可以在类级别或方法级别使用@IfProfileValue。@IfProfileValue的类级别使用优先于该类或其子类中的任何方法。具体而言,如果在类级别和方法级别启用了测试,则启用测试。@IfProfileValue的缺失意味着该测试是隐式启用的。这类似于Junit 4的@Ignore注解的语义,只是@Ignore的存在总是禁用测试。
以下示例显示了具有@IfProfileValue的测试:
@IfProfileValue(name="java.vendor", value="Oracle Corporation") //仅当Java供应商为“ Oracle Corporation”时,才运行此测试。
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
另外,您可以使用values列表(OR语义)配置@IfProfileValue,以在Junit 4环境中实现对测试组的类似TestNG的支持。考虑以下示例:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) //单元测试和集成测试。
@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)
public class CustomProfileValueSourceTests {
// class body...
}
@Timed
@Timed表示,带注解的测试方法必须在指定的时间段(以毫秒为单位)完成执行。如果测试执行时间超过指定的时间段,则测试失败。
时间段包括运行测试方法本身,任何重复测试(请参阅@Repeat),以及任何设置或拆除测试fixture的时间。以下示例显示了如何使用它:
@Timed(millis = 1000)
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to run
}
Spring的@Timed注解的语义与Junit 4的@Test(timeout =…)支持不同。具体而言,由于Junit 4处理测试执行超时的方式(即,通过在单独线程中执行测试方法),@Test(timeout =…)如果测试花费太长,则抢占式地测试失败。另一方面,Spring的@Timed并没有抢占式失败测试,而是要等待测试在失败之前完成。
@Repeat
@Repeat表示必须重复运行带该注解的测试方法。在注解中指定了要运行测试方法的次数。
要重复执行的范围包括测试方法本身的执行以及任何设置或拆除测试fixture。以下示例显示了如何使用@Repeat注释:
@Repeat(10)
@Test
public void testProcessRepeatedly() {
// ...
}
# 3.4.4. Spring JUnit Jupiter测试注解
与SpringExtension (opens new window)和Junit Jupiter(即Junit 5中的编程模型)一起使用时,支持以下注解:
@SpringJUnitConfig(opens new window)@SpringJUnitWebConfig(opens new window)@TestConstructor(opens new window)@NestedTestConfiguration(opens new window)@EnabledIf(opens new window)@DisabledIf(opens new window)
@SpringJUnitConfig
@SpringJUnitConfig是一个复合的注释,它结合了Spring TestContext 框架中的@ContextConfiguration和 JUnit Jupiter的@ExtendWith(SpringExtension.class)。它可以在类级别用作@ContextConfiguration的替换。关于配置选项,@ContextConfiguration和@SpringJUnitConfig之间的唯一区别是,可以在@SpringJUnitConfig中使用value属性来声明组件类。
以下示例显示了如何使用@SpringJUnitConfig指定配置类:
@SpringJUnitConfig(TestConfig.class)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
以下示例显示了如何使用@SpringJUnitConfig来指定配置文件的位置:
@SpringJUnitConfig(locations = "/test-config.xml")
class XmlJUnitJupiterSpringTests {
// class body...
}
有关更多详细信息,请参见Context Management (opens new window)和@SpringJUnitConfig (opens new window) 及@ContextConfiguration的javadoc。
@SpringJUnitWebConfig
@SpringJUnitWebConfig是一个复合的注释,它结合了Spring TestContext 框架中的@WebAppConfiguration和 JUnit Jupiter的@ExtendWith(SpringExtension.class)。它可以在类级别用作@ContextConfiguration和@WebAppConfiguration的替换。关于配置选项,@ContextConfiguration和@SpringJUnitWebConfig之间的唯一区别是,可以在@SpringJUnitWebConfig中使用value属性来声明组件类。此外,您只能通过在@SpringJUnitWebConfig中使用resourcePath属性来覆盖@WebAppConfiguration的value属性。
以下示例显示了如何使用@SpringJUnitWebConfig来指定配置类:
@SpringJUnitWebConfig(TestConfig.class)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
以下示例显示了如何使用@SpringJUnitWebConfig来指定配置文件的位置:
@SpringJUnitWebConfig(locations = "/test-config.xml")
class XmlJUnitJupiterSpringWebTests {
// class body...
}
有关更多详细信息,请参见Context Management (opens new window)和 @SpringJUnitWebConfig (opens new window), @ContextConfiguration (opens new window)及@WebAppConfiguration (opens new window) 的javadoc。
@TestConstructor
@TestConstructor是一种类级注解,用于配置测试类构造函数的参数如何从测试ApplicationContext中的组件注入。
如果@TestConstructor在测试类中不存在或 meta-present,则将使用默认的test constructor autowire mode。有关如何更改默认模式的详细信息,请参见下面的提示。但是请注意,构造函数上@Autowired的本地声明优先于@TestConstructor和默认模式。
💡更改默认的测试构造函数自动注入模式
可以通过将spring.test.constructor.autowire.mode JVM 系统属性设置为all,更改默认测试构造器自动注入模式。或者,可以通过SpringProperties机制设置默认模式。
从Spring 5.3开始,默认模式也可以配置为 JUnit Platform configuration parameter (opens new window)。
如果未设置spring.test.constructor.autowire.mode属性,测试类的构造器不会自动注入。
ℹ️从Spring框架5.2开始,@TestConstructor仅支持与SpringExtension和 JUnit Jupiter一起使用。请注意,通常会自动为您注册SpringExtension - 例如,当使用@SpringJUnitConfig和@SpringJUnitWebConfig或各种来自Spring Boot测试相关注解时。
@NestedTestConfiguration
@NestedTestConfiguration是一种类型级注解,用于配置在为内部测试类封闭类层次结构中如何 处理Spring测试配置注解。
如果@NestedTestConfiguration不存在或meta-present,在它的超类层次结构或其包装类层次结构中,将使用默认的enclosing configuration inheritance mode 。有关如何更改默认模式的详细信息,请参见下面的提示。
💡 更改默认的enclosing configuration inheritance mode
默认的enclosing configuration inheritance mode是INHERIT,可以通过将spring.test.enclosing.configuration JVM 系统属性设置为OVERRIDE来更改它。或者,可以通过SpringProperties机制设置默认模式。
Spring TestContext Framework (opens new window) 框架为以下注解提供了@NestedTestConfiguration语义。
@BootstrapWith(opens new window)@ContextConfiguration(opens new window)@WebAppConfiguration(opens new window)@ContextHierarchy(opens new window)@ActiveProfiles(opens new window)@TestPropertySource(opens new window)@DynamicPropertySource(opens new window)@DirtiesContext(opens new window)@TestExecutionListeners(opens new window)@Transactional(opens new window)@Commit(opens new window)@Rollback(opens new window)@Sql(opens new window)@SqlConfig(opens new window)@SqlMergeMode(opens new window)@TestConstructor(opens new window)
ℹ️@NestedTestConfiguration的使用通常仅与Junit Jupiter中的@Nested测试类结合在一起。但是,可能还有其他测试框架,并支持使用Spring和此注解的嵌套测试类。
参考@Nested test class configuration (opens new window) 获取更多细节。
@EnabledIf
@EnabledIf表示如果提供的expression为true,则应运行JUnit Jupiter 测试类或方法。具体而言,如果表达式计算为Boolean.TRUE或等于字符串true(忽略大小写),则启用测试。当在类级别应用时,该类中的所有测试方法默认情况下也将自动启用。
表达式可以是以下任何一个:
- Spring Expression Language (opens new window) (SpEL) expression.例如:
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}") - Spring
Environment(opens new window)中可用属性占位符.例如:@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表示如果提供的expression为true,则不运行JUnit Jupiter 测试类或方法。具体而言,如果表达式计算为Boolean.TRUE或等于字符串true(忽略大小写),则禁用测试。当在类级别应用时,该类中的所有测试方法默认情况下也将自动禁用。
表达式可以是以下任何一个:
- Spring Expression Language (opens new window) (SpEL) expression. 例如:
@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}") - Spring
Environment(opens new window)中可用属性占位符, 例如:@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. 测试元注解支持
您可以将大多数与测试相关的注解用作 meta-annotations (opens new window) ,以创建自定义组成的注解,并减少在测试套件中的重复配置。
您可以将以下每个用作与 TestContext framework (opens new window)结合使用的元注解。
@BootstrapWith@ContextConfiguration@ContextHierarchy@ActiveProfiles@TestPropertySource@DirtiesContext@WebAppConfiguration@TestExecutionListeners@Transactional@BeforeTransaction@AfterTransaction@Commit@Rollback@Sql@SqlConfig@SqlMergeMode@SqlGroup@Repeat(only supported on JUnit 4)@Timed(only supported on JUnit 4)@IfProfileValue(only supported on JUnit 4)@ProfileValueSourceConfiguration(only supported on JUnit 4)@SpringJUnitConfig(only supported on JUnit Jupiter)@SpringJUnitWebConfig(only supported on JUnit Jupiter)@TestConstructor(only supported on JUnit Jupiter)@NestedTestConfiguration(only supported on JUnit Jupiter)@EnabledIf(only supported on JUnit Jupiter)@DisabledIf(only supported on 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 Programming Model (opens new window) wiki页面。
# 3.5. Spring TestContext 框架
Spring TestContext Framework(位于org.springframework.test.context软件包中)提供了通用的,注解驱动的单元和集成测试支持。TestContext Framework还对覆盖配置提供重要且方便的机制,可以通过基于注解的配置覆盖合理的默认值。
除了通用测试基础架构外,TestContext框架还为Junit 4,Junit Jupiter(又名Junit 5)和TestNG提供明确的支持。对于Junit 4和TestNG,Spring提供抽象支持类。此外,Spring为Junit 4提供了自定义的Junit Runner和自定义Junit Rules,以及Junit Jupiter的自定义Extension,可让您编写所谓的POJO测试类。POJO测试类不需要扩展特定的类层次结构,例如abstract支持类。
以下部分概述了TestContext Framework的内部细节。如果您只对使用该框架感兴趣,并且对自定义监听器或自定义加载器不感兴趣,请随时直接转到配置(context management (opens new window), dependency injection (opens new window), transaction management (opens new window)),support classes (opens new window),和annotation support (opens new window) 部分。
# 3.5.1. 关键抽象
框架的核心由TestContextManager类和TestContext,TestExecutionListener和SmartContextLoader接口组成。为每个测试类创建了一个TestContextManager(例如,用于在Junit Jupiter中的单个测试类中执行所有测试方法)。反过来,TestContextManager管理了一个具有当前测试上下文的TestContext。TestContextManager还随着测试的进展委托TestExecutionListener实现来更新测试的状态,该实现通过提供依赖注入,管理事务等等来启动实际的测试执行。SmartContextLoader负责为给定的测试类加载ApplicationContext。有关各种实现的更多信息和示例,请参见javadoc (opens new window)和Spring Test 套件 。
TestContext
TestContext封装了运行测试的上下文(实际测试框架不可知),并为其负责的测试实例提供上下文管理和缓存支持。TestContext还委托给SmartContextLoader加载ApplicationContext(如果要求)。
TestContextManager
TestContextManager是Spring TestContext 框架的主要入口,负责管理在定义好的测试执行入口注册的TestExecutionListener的每个信号事件和每个TestContext:
- 在任何特定测试框架的“before class”或“before all”方法之前。
- 测试实例后置处理。
- 在任何特定测试框架的“before”或“before each”方法之前。
- 在执行测试方法之前,但在测试设置之后。
- 执行测试方法后,但在测试移除之前。
- 在任何特定测试框架的“after”或“after each”方法之后。
- 在任何特定测试框架的“after class”或“after all”方法之后。
TestExecutionListener
TestExecutionListener定义了API,使用注册的监听器对TestContextManager发布的测试执行事件做出反应。请参阅TestExecutionListener Configuration (opens new window)。
Context Loaders
ContextLoader是一个策略接口,用于为由Spring TestContext 框架管理的集成测试加载ApplicationContext。您应该实现SmartContextLoader而不是此接口,以提供组件类,激活bean定义配置文件,测试属性源,上下文层次结构和WebApplicationContext支持。
SmartContextLoader是ContextLoader接口的扩展,它取代了原始的最小上下文加载器SPI。具体而言,SmartContextLoader可以选择处理资源位置,组件类或上下文初始化器。此外,SmartContextLoader可以在加载的上下文中设置激活的bean定义配置文件和测试属性源。
Spring提供了以下实现:
DelegatingSmartContextLoader: 两个默认加载程序之一,它内部委派给AnnotationConfigContextLoader,GenericXmlContextLoader或GenericGroovyXmlContextLoader,取决于在测试类声明的配置或默认位置或默认配置类中所声明的配置。仅当Groovy在类路径上时,才能启用Groovy支持。WebDelegatingSmartContextLoader: 两个默认加载程序之一,它内部委派给AnnotationConfigWebContextLoader,GenericXmlWebContextLoader或GenericGroovyXmlWebContextLoader,取决于在测试类声明的配置或默认位置或默认配置类中所声明的配置。仅当测试类中存在@WebAppConfiguration时,才会使用web ContextLoader。仅当仅当Groovy在类路径上时,才能启用Groovy支持。AnnotationConfigContextLoader: 从组件类加载标准的应用程序。AnnotationConfigWebContextLoader: 从组件类加载WebApplicationContext。GenericGroovyXmlContextLoader:从资源位置加载标准的ApplicationContext,这些资源是Groovy脚本或XML配置文件。GenericGroovyXmlWebContextLoader:从Groovy脚本或XML配置文件中加载WebApplicationContext。GenericXmlContextLoader: 从XML资源位置加载ApplicationContext。GenericXmlWebContextLoader: 从XML资源位置加载WebApplicationContext。
# 3.5.2. 引导测试TestContext 框架
Spring TestContext 框架的内部的默认配置足以满足所有常见使用场景。但是,有时开发团队或第三方框架希望更改默认的ContextLoader,实现自定义TestContext或ContextCache,增强ContextCustomizerFactory和TestExecutionListener实现的默认集,等等。对于对TestContext框架如何运行的如此底层控制,Spring提供了引导策略。
TestContextBootstrapper定义了用于引导TestContext框架的SPI。TestContextBootstrapper由TestContextManager使用用于加载测试的TestExecutionListener实现,并构建其管理的TestContext。您可以直接使用@BootstrapWith或使用@BootstrapWith或作为元注解,为测试类(或测试类层次结构)配置自定义的引导策略。如果未使用@BootstrapWith明确配置引导程序,则使用DefaultTestContextBootstrapper或WebTestContextBootstrapper,具体取决于是否存在@WebAppConfiguration。
由于TestContextBootstrapperSPI可能会在将来发生变化(以适应新的要求),因此我们强烈鼓励实现者不要直接实施此接口,而是要继承AbstractTestContextBootstrapper或其具体子类之一。
# 3.5.3. TestExecutionListener 配置
Spring提供按以下顺序注册的TestExecutionListener实现:
ServletTestExecutionListener: 为WebApplicationContext配置Servlet API mocks 。DirtiesContextBeforeModesTestExecutionListener: 处理“before”模式的@DirtiesContext注解。DependencyInjectionTestExecutionListener: 为测试实例提供依赖注入。DirtiesContextTestExecutionListener: 处理“after”模式的@DirtiesContext注解。TransactionalTestExecutionListener: 提供默认回滚语义的事务测试执行。SqlScriptsTestExecutionListener: 运行使用@Sql注解配置的SQL脚本。EventPublishingTestExecutionListener: 将测试执行事件发布到测试的ApplicationContext(请参阅Test Execution Events (opens new window))。
注册TestExecutionListener 实现
您可以使用@TestExecutionListeners注解来注册测试类及其子类的TestExecutionListener实现。有关详细信息和示例,请参见 @TestExecutionListeners (opens new window)的annotation support (opens new window) javadoc。
自动发现默认TestExecutionListener 实现
使用@TestExecutionListeners注册TestExecutionListener实现,适用于在有限的测试方案中使用的自定义监听器。但是,如果需要在整个测试套件中使用自定义监听器,则可能会变得笨拙。通过SpringFactoriesLoader机制自动发现默认的TestExecutionListener实现来解决此问题。
具体而言,spring-test模块META-INF/spring.factories属性文件中声明所有核心默认TestExecutionListener实现。第三方框架和开发人员可以通过自己的META-INF/spring.factories属性文件,以相同的方式将自己的TestExecutionListener实现贡献给默认监听器列表。
排序TestExecutionListener 实现
当TestContext框架通过上述SpringFactoriesLoader机制发现默认的TestExecutionListener实现时,通过使用Spring的AnnotationAwareOrderComparator对实例化的监听器进行排序,该AnnotationAwareOrderComparator接受Spring的Ordered接口和 @Order注解。由Spring提供的AbstractTestExecutionListener和所有默认的TestExecutionListener实现,以合适的值实现了Ordered接口。因此,第三方框架和开发人员应确保通过实现Ordered或声明 @Order来以适当的顺序注册其默认的TestExecutionListener实现。有关核心默认TestExecutionListener实现的getOrder()方法,请参见javadoc获取有关分配给每个核心监听器的值。
合并TestExecutionListener实现
如果通过@TestExecutionListeners注册自定义TestExecutionListener,则不会注册默认的监听器。在大多数常见的测试方案中,这有效地迫使开发人员除了任何自定义监听器外还需手动声明所有默认监听器。如以下清单所示:
@ContextConfiguration
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class
})
class MyTest {
// class body...
}
这种方法面临的挑战是,它要求开发人员确切知道哪些监听器是默认注册的。此外,一组默认的监听器可以从发行版中更改,例如,SqlScriptsTestExecutionListener是在Spring 4.1中引入的,DirtiesContextBeforeModesTestExecutionListener在Spring 4.2中引入。此外,通过使用上述自动发现机制,诸如Spring Boot和Spring Security 之类的第三方框架注册自己的默认TestExecutionListener实现。
为了避免必须了解并重新声明所有默认的监听器,您可以将@TestExecutionListeners的mergeMode属性设置为MergeMode.MERGE_WITH_DEFAULTS。MERGE_WITH_DEFAULTS表明应将本地声明的监听器与默认监听器合并。合并算法确保从列表中删除重复配置项,并根据AnnotationAwareOrderComparator的语义对所得的合并后的监听器列表进行排序。如果监听器实现Ordered或用@Order注解,则可以影响与默认值合并的位置。否则,合并后,将本地声明的监听器追加到默认听众列表中。
例如,如果上一个示例中的MyCustomTestExecutionListener类配置其order值(例如,500)小于ServletTestExecutionListener的顺序(1000),则可以将MyCustomTestExecutionListener与默认列表自动合,且其顺序在ServletTestExecutionListener的前面,前一个示例可以用以下形式代替:
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
# 3.5.4. 测试执行事件
Spring框架5.2中引入的EventPublishingTestExecutionListener提供了实现自定义TestExecutionListener的替代方法。测试ApplicationContext中的组件可以监听EventPublishingTestExecutionListener发布的以下事件,每个事件都与TestExecutionListenerAPI中的方法相对应。
BeforeTestClassEventPrepareTestInstanceEventBeforeTestMethodEventBeforeTestExecutionEventAfterTestExecutionEventAfterTestMethodEventAfterTestClassEvent
这些事件仅在已加载ApplicationContext时才发布。
这些事件可能因各种原因而被消费,例如重置mock bean或跟踪测试执行。消费测试执行事件而不是实现自定义TestExecutionListener的优点是,在测试应用程序中注册的任何Spring bean可以消费测试执行事件,并且此类bean可以直接从依赖注入和ApplicationContext的其他功能中受益。相比之下,TestExecutionListener不是ApplicationContext中的bean。
为了监听测试执行事件,Spring Bean可以选择实现org.springframework.context.ApplicationListener接口。另外,可以用@EventListener注解监听器方法,并配置为监听上面列出的特定事件类型之一。由于这种方法的普及,Spring提供了以下专用@EventListener注解,以简化测试执行事件监听器的注册。这些注解位于org.springframework.test.context.event.annotation软件包中。
@BeforeTestClass@PrepareTestInstance@BeforeTestMethod@BeforeTestExecution@AfterTestExecution@AfterTestMethod@AfterTestClass
异常处理
默认情况下,如果测试执行事件监听器在消费事件时会引发异常,则该异常将传播到使用中的基础测试框架(例如JUnit或TestNG)。例如,如果消费BeforeTestMethodEvent会导致异常,则相应的测试方法将因异常而失败。相反,如果异步测试执行事件监听器会引发异常,则该异常不会传播到基础测试框架。有关异步异常处理的更多详细信息,请参考@EventListener的类级别javadoc。
异步监听器
如果您希望特定的测试执行事件监听器异步处理事件,则可以使用Spring的regular @Async support (opens new window).。有关更多详细信息,请参考@EventListener的类级别javadoc。
# 3.5.5. 上下文管理
每个TestContext为其负责的测试实例提供上下文管理和缓存支持。测试实例不会自动接收对已配置的ApplicationContext的访问。但是,如果测试类实现了ApplicationContextAware接口,则将提供ApplicationContext的引用给测试实例。请注意,AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests实现了ApplicationContextAware,因此可以自动提供对ApplicationContext的访问。
💡@Autowired ApplicationContext
作为实现ApplicationContextAware接口的替代方法,您可以通过@Autowired注解在字段或Setter方法上注入测试类的应用程序上下文,如下示例显示:
@SpringJUnitConfig class MyTest { @Autowired ApplicationContext applicationContext; // class body... }同样,如果您的测试配置为加载WebApplicationContext,则可以将Web应用程序上下文注入测试中,如下所示:
@SpringJUnitWebConfig class MyWebAppTest { @Autowired WebApplicationContext wac; // class body... }通过@Autowired的依赖注入由DependencyInjectionTestExecutionListener提供,默认情况下是配置的(请参阅 Dependency Injection of Test Fixtures (opens new window))。
使用TestContext框架的测试类不需要继承任何特殊的类或实现特定的接口去配置它们的应用上下文,在类级别上使用@ContextConfiguration注解就能完成配置。如果你的测试类不需要显示地声明应用上下文的资源位置或组件类,配置好的ContextLoader决定了如果从默认位置或configuration类中加载上下文。除了上下文资源位置和组件类,应用上下文也能通过上下文初始化器配置。
下面章节解释了如何利用Spring的@ContextConfiguration注解,通过XML配置文件,Groovy脚本,组件类(通常是@Configuration类)或者上下文初始化器来配置测试的ApplicationContext。另外你可以实现和配置你的自定义SmartContextLoader以应对更复杂的使用场景。
# 3.5.5.1使用XML资源配置Context
为了使用XML配置文件加载测试的ApplicationContext,用@ContextConfiguration注解你的测试类,并使用包含XML配置元数据资源位置的数组配置locations属性。原始或相对路径(例如,context.xml)会被当作相对于测试类所在包的类路径资源。以/开头的路径会被当作绝对路径(例如,/org/example/config.xml)。代表URL资源的路径(例如,以classpath:,file:,http:开头的路径)即为其本意。
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
class MyTest {
// class body...
}
@ContextConfiguration支持用标准的Java value属性作为locations的别名。因此,如果你不需要声明别的属性,你可以省略locations,参考以下示例:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
class MyTest {
// class body...
}
如果你把locations和value都省略,TestContext框架将尝试检测默认的XML资源路径。具体来说,GenericXmlContextLoader和GenericXmlWebContextLoader将基于测试类名来寻找默认资源路径。如果你的类名为com.example.MyTest,GenericXmlContextLoader将从classpath:com/example/MyTest-context.xml加载你的应用上下文,参考以下示例:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration
class MyTest {
// class body...
}
# 3.5.5.2使用Groovy脚本配置Context
要用使用 Groovy Bean Definition DSL (opens new window)的Groovy脚本来加载ApplicationContext,需要用@ContextConfiguration注解你的测试类,并使用包含Groovy脚本位置的数组配置locations或value属性。Groovy脚本的资源查找语义与XML配置文件类似。
💡启用Groovy脚本支持
如果Groovy在classpath存在,Spring TestContext框架自动启用对Groovy脚本加载
ApplicationContext的支持。
以下示例显示了如何定义Groovy配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})
class MyTest {
// class body...
}
如果你把locations和value都省略,TestContext框架将尝试检测默认的Groovy脚本。具体来说,GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader将基于测试类名来寻找默认资源路径。如果你的类名为com.example.MyTest,Groovy上下文加载器将从classpath:com/example/MyTestContext.groovy加载你的应用上下文,参考以下示例:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration
class MyTest {
// class body...
}
💡同时声明XML配置和Groovy脚本
你可以在
@ContextConfiguration的locations和value属性中同时声明XML配置和Groovy配置。如果资源路径以.xml结尾,它将被XmlBeanDefinitionReader加载,否则,将使用GroovyBeanDefinitionReader加载。@ExtendWith(SpringExtension.class) // ApplicationContext will be loaded from // "/app-config.xml" and "/TestConfig.groovy" @ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" }) class MyTest { // class body... }
# 3.5.5.3使用Component类配置Context
要使用component类来加载ApplicationContext,你可以用@ContextConfiguration注解你的测试类,并使用包含组件类数组配置classes属性,参考以下示例:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
class MyTest {
// class body...
}
💡组件类
以下任意类都可称为“组件类”:
- 使用
@Configuration注解的类- component类(被
@Component,@Service,@Repository或其他原型注解标注的类)- 符合JSR-330的被
javax.inject系列注解标注的类- 任何含
@Bean方法的类- 任何作为Spring组件注册的类(例如
ApplicationContext中的Spring bean),其有可能在不使用Spring注解的情况下利用单构造器的自动注入
如果你把claases省略,TestContext框架将尝试检测默认的配置类。具体来说,AnnotationConfigContextLoader和AnnotationConfigWebContextLoader将检测测试类内部的static配置类。配置类的名称是任意的。另外,测试类内部可有多个static配置类。参考以下示例:
@SpringJUnitConfig
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {
@Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
@Autowired
OrderService orderService;
@Test
void testOrderService() {
// test the orderService
}
}
# 3.5.5.4 结合XML,Groovy脚本和Component类
有时可能希望将XML配置文件,Groovy脚本和组件类结合使用来配置测试的ApplicationContext。例如,如果你在生产环境使用XML配置,你可能想用@Configuration类配置特殊的由Spring管理的组件,或者相反。
因此,一些三方框架(例如Spring Boot)提供了同时从不同类型资源加载ApplicationContext的良好支持。由于历史原因,Spring框架对标准部署未提供此支持。因此,Spring框架在spring-test模块提供的SmartContextLoader实现针对每个测试上下文只支持一种类型资源。但这并不意味着你不能同时使用它们。一般规则的一个例外是GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader同时支持Groovy脚本和XML配置。因此,三方框架可选择通过@ContextConfiguration同时支持locations和classes的声明。而且在TestContext框架的标准测试支持下,你可以使用以下选项。
如果要使用资源位置(例如,XML或Groovy)和@Configuration类来配置测试,你必须选择一个作为入口点,用它来include或import另一个。例如,在XML或Groovy脚本中,你可以使用组件扫描或定义Spring beans来包含@Configuration类。注意此行为在语义上等同于如何在生产中配置应用程序:在生产配置中,你可以定义一组加载你生产ApplcationContext的XML或Groovy资源位置,或一组@Configuration类,但是你仍然可自由选择include或import其他类型的配置。
# 3.5.5.5 使用Context初始化器配置Context
要使用context初始化器配置ApplicationContext,用@ContextConfiguration注解你的测试类,并使用包含实现ApplicationContextInitializer的类的引用数组配置initializers属性。
被声明的上下文初始化器将用来
初始化ConfigurableApplicationContext,后者用来加载你的测试。注意被每个初始化器声明的具体的ConfigurableApplicationContext类型必须与使用中的SmartContextLoader(通常是GenericApplicationContext)创造的ApplicationContext类型兼容。因此,初始化器被调用的顺序取决于他们是否实现Spring的Ordered接口或被Spring的@Order或@Priority注解。参考以下示例:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class)
class MyTest {
// class body...
}
你可以不在@ContextConfiguration中声明XML配置文件,Groovy脚本或组件类而仅仅只声明ApplicationContextInitializer类,后者负责在上下文中注册bean--例如,编码化从XML文件或配置类中加载bean定义,参考以下示例:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class)
class MyTest {
// class body...
}
# 3.5.5.6 Context配置继承
@ContextConfiguration支持boolean值的inheritLocations和inheritInitializer属性,它们决定由父类声明的资源位置或组件类和上下文初始化器是否被继承。它们的默认值都是true。这意味着测试类会继承父类声明的资源位置或组件类或上下文初始化器。特别的,一个测试类的资源位置或组件类将被附加在被父类声明的资源位置列表或注解类中。同样的,给定测试类的初始化器也被添加到父类声明的初始化器集合中。因此,子类可选继承资源位置,组件类或上下文初始化器。
如果@ContextConfiguration中的inheritLocations或inheritInitializer值为false,测试类中的资源位置或组件类和上下文初始化器将各自
替代父类中定义的配置。
截至Spring 5.3,测试配置也可继承自闭包类。详情参考
@Nestedtest class configuration (opens new window)
在下面使用XML资源位置的配置中,ExtendedTest的ApplicationContext按序加载自base-config.xml和extended-config.xml。在extended-config.xml中定义的bean因此能覆盖base-config.xml中定义的bean。以下示例类展示了如何使用继承并使用它们自己和父类的配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml")
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml")
class ExtendedTest extends BaseTest {
// class body...
}
相似的,在下面使用组件类的配置中,ExtendedTest的ApplicationContext按序加载自BaseConfig和ExtendedConfig类。在ExtendedConfig中定义的bean因此能覆盖BaseConfig中定义的bean。以下示例类展示了如何使用继承并使用它们自己和父类的配置类:
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class)
class ExtendedTest extends BaseTest {
// class body...
}
在下面使用初始化器的配置中,ExtendedTest的ApplicationContext初始化自BaseInitializer和ExtendedInitializer类。注意加载器调用的顺序区取决于它们是否实现Spring的Ordered接口或使用@Order或@Priority注解。以下示例类展示了如何使用继承并使用它们自己和父类的初始化器:
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class)
class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class)
class ExtendedTest extends BaseTest {
// class body...
}
# 3.5.5.7 使用环境配置文件配置Context
Spring框架良好支持环境和配置文件(也叫bean定义配置文件),集成测试可以为各种测试场景配置激活特定的bean定义配置文件。通过使用@ActiveProfiles注解并提供应被激活的配置列表来加载测试的ApplicationContext。
你可以使用
SmartContextLoaderSPI的各种实现,但是@ActiveProfiles不支持旧的ContextLoaderSPI的实现。
参考使用XML配置和@Configuration的两个例子:
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<bean id="transferService"
class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository"
class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy"
class="com.bank.service.internal.ZeroFeePolicy"/>
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script
location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
<beans profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
TransferServiceTest运行时, ApplicationContext根据类根路径的app-config.xml配置文件加载。如果你检查app-config.xml,可以发现accountRepository依赖了dataSource。但是dataSource不是顶层bean。相反,dataSource被定义了三次:production文件,dev文件,defaut文件。
通过使用@ActiveProfiles("dev")注解TransferServiceTest,我们让Spring TestContext框架激活{"dev"}配置文件来加载ApplicationContext,这样就创建并用测试数据库填充了嵌入式数据库,并且accountRepository包装了一个DataSource的引用。
有时候设置default配置文件很有用,默认配置中的beans仅在另外两个配置文件未被激活的情况下引入。你可以使用此特性定义应用程序默认状态的"fallback"beans。例如,你可以明确提供dev和production的数据源,但同时定义一个默认的基于内存的数据源。
以下代码展示了如何用@Configuration实现相同的配置和继承测试:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
@Configuration
public class TransferServiceConfig {
@Autowired DataSource dataSource;
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
我们把XML配置分成四个独立的@Configuration类:
TransferServiceConfig:通过使用@Autowired依赖注入获得dataSource。StandaloneDataConfig:定义开发测试适合的dataSource。JndiDataConfig:生产环境中从JNDI检索的dataSource。DefaultDataConfig:未激活配置文件时默认的dataSource。
@ActiveProfiles也支持inheritProfiles属性,可用来禁用继承来的激活配置文件:
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
因此,有时有必要编程式解析激活的配置文件而不是声明式地--例如基于:
- 当前操作系统
- 是否在连续继承构建服务器上测试
- 确定的环境变量存在
- 自定义类级别的注解存在
- 其他关注点
要编程式激活bean定义配置文件,你可以实现自定义的ActiveProfilesResolver并使用@ActiveProfiles的resolver的属性注册它。更多信息参考 javadoc (opens new window)。以下示例展示了如何实现并注册自定义的OperatingSystemActiveProfilesResolver:
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// t
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// t
# 3.5.5.8 使用测试属性文件配置Context
Spring框架良好支持property文件,你可以使用测试特定的属性文件配置集成测试。与@PropertySource注解不同,你可以在测试类上声明@TestPropertySource注解以加载测试属性文件或内联属性文件。这些测试属性文件将被添加到Environment的PropertySources中,以此来加载集成测试的ApplicationContext。
你可以在
SmartContextLoaderSPI的实现中使用@TestPropertySource,但后者不支持ContextLoaderSPI的实现。
SmartContextLoader的实现通过MergedContextConfiguration中的getPropertySourcelocations()和getPropertySourceProperties()方法来合并测试属性文件源。
声明测试属性源文件
你可以使用@TestPropertySource的locations或value属性配置测试属性文件源。传统的和基于XML的属性文件格式都支持--例如,classpath:/com/example/test.properties和file:///path/to/file.xml。
每个路径都被当作Spring Resource。普通路径如"test.properties"被当作当对于测试类所在包的类路径资源。以斜杠开头的路径视为绝对路径如"/org/example/test.xml"。引用URL的路径通过特定协议加载(如classpath:,file:,http:)。资源通配符如*/.properties是不允许的:每个路径必须指向唯一的.properties或.xml资源。
@ContextConfiguration
//使用绝对路径定义测试资源文件
@TestPropertySource("/test.properties")
class MyIntegrationTests {
// class body...
}
你可以使用@TestPropertySource的properties属性通过key-value形式定义内联属性。key-value对将被作为最高优先级的测试PropertySource添加到闭包的Environment中。
支持的key-value语法跟Java properties中定义的一样:
- key=value
- key:value
- key value
以下示例设置了两个内联属性:
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"})
class MyIntegrationTests {
// class body...
}
截止Spring 5.2,
@TestPropertySource可以作为可重复注解使用。这意味着你在一个测试类上可声明多个@TestPropertySource,前者locations和properties中的值将被后声明的覆盖。此外,您可以在测试类上声明多个由
@TestPropertySource元注解的组合注解,并且所有这些@TestPropertySource声明都将生效。直接声明的
@TestPropertySource优先级高于作为元注解的@TestPropertySource。也就是说,直接声明的@TestPropertySource中的locations和properties属性值将覆盖作为元注解的属性对应值。
默认属性文件探测
如果@TestPropertySource声明为空注解(也就是不配置locations和properties属性),spring将会探测与使用此注解的类相关的默认属性文件。例如,如果测试类为com.example.MyTest,对应的默认属性文件是classpath:com/example/MyTest.properties。如果探测不到默认文件,将抛出IllegalStateException异常。
优先级
测试属性文件相对于操作系统环境中的文件、Java系统配置或由@PropertySource声明、编程式声明的配置文件拥有更高的优先级。因此,测试配置可以覆盖系统和程序配置。此外内联配置比从资源文件中加载的配置优先级更高。但通过@DynamicPropertySource注册的属性比通过@TestPropertySource注册的优先级更高。
在下面的示例中,timezone和port属性和定义在"/test.properties"中的属性值会覆盖系统和程序配置源文件中定义的同名属性。而且"/test.properties"中定义的timezone和port属性也会被properties内联属性覆盖。
@ContextConfiguration
@TestPropertySource(
locations = "/test.properties",
properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
// class body...
}
继承和覆盖测试属性源
@TestPropertySource支持inheritLocations和inheritProperties属性,它们象征着是否应该继承父类中声明的资源文件中的属性和内联属性。默认值都是true。也就是说,子类的locations和内联属性将会添加到父类中声明的属性中去。注意后定义的属性将覆盖早定义的同名属性。另外前面提到的优先级适用于继承的测试属性。
如果@TestPropertySource的inheritLocations和inheritProperties属性值为false,子类中的属性将覆盖父类中的属性。
截至Spring 5.3,测试配置也可以在闭包类中继承,详见
@Nestedtest class configuration (opens new window) 。
下面的示例中,BaseTest的ApplicationContext仅使用base.properties加载,而ExtendedTest的使用base.properties和extended.properties加载。
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
下面的示例中,BaseTest的ApplicationContext仅使用内联key1属性加载,而ExtendedTest的使用key1和key2加载。
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}