Spring TestContext Framework(包路径为 org.springframework.test.context)提供了通用的、注解驱动的单元和集成测试支持,这些支持与使用的测试框架无关。TestContext框架还非常重视约定优于配置的约定,可以通过基于注释的配置重写。
除了通用的测试基础结构之外,TestContext框架还以抽象支持类的形式提供了对JUnit 4和TestNG的明确支持。对于JUnit 4,Spring还提供了一个自定义的JUnit运行器和自定义JUnit规则,允许编写所谓的POJO测试类。不需要POJO测试类来扩展特定的类层次结构。
下一节将概述TestContext框架的内部结构。如果您只对使用框架感兴趣,而不希望使用您自己的自定义侦听器或自定义加载器来扩展它,那么您可以直接访问配置(上下文管理、依赖注入、事务管理)、支持类和注释支持部分。
框架的核心包括TestContextManager类,TestContext接口,TestExecutionListener接口和SmartContextLoader接口。每个测试类将会创建一个TestContextManager (JUnit 4中一个测试类下的所有测试)。 TestContextManager 管理一个保存目前测试上下文的TestContext 。TestContextManager在测试过程中更新了TestContext的状态,并将其委托给TestExecutionListener操作,后者通过提供依赖注入、管理事务等方式来支持执行测试。SmartContextLoader负责为给定的测试类加载一个ApplicationContext。请参阅javadocs和Spring测试套件,以获得更多信息和各种实现的示例。
TestContext封装了执行测试的上下文,隔离了实际测试框架的使用情况,并为对应的测试实例提供了上下文管理和缓存支持。TestContext 在需要的情况下,也提供了加载ApplicationContext 的ApplicationContext 。
其是Spring TestContext Framework主要切点,其管理了一个TestContext ,并在测试执行切点,为注册的TestExecutionListener 提供信号事件。切点包括:
- 特定测试框架的所有的类before或所有方法的before之前
- 测试实例post-processing
- 特定测试框架的每个before或每个方法的before之前
- 特定测试框架的每个after或每个方法的after之后
- 特定测试框架的所有after或所有方法的after之后
TestExecutionListener 定义了响应TestContextManager 发出的测试执行事件的接口。阅读Section 15.5.3, “TestExecutionListener configuration”了解详情。
ContextLoader 自Spring 2.5引入,是为了Spring TestContext Framework中一个集成测试加载ApplicationContext 定义的策略接口。SmartContextLoader接口的实现,提供了对带注释的类、活动bean定义Profile、测试属性源、上下文层次结构和WebApplicationContext的支持。
SmartContextLoader 是ContextLoader 接口的扩展,自Spring 3.1引入。SmartContextLoader 定义的SPI取代了ContextLoader的SPI. 值得关注的是,SmartContextLoader 可以配置资源locations,注解类,上下文initializers。此外,SmartContextLoader 还可以在上下文中定义活跃bean的profiles和测试属性源。
spring提供了以下的实现:
- DelegatingSmartContextLoader。两个默认加载器的其中一个。按照测试类的配置,默认位置配置或默认配置类的配置信息,使用AnnotationConfigContextLoader,GenericXmlContextLoader或GenericGroovyXmlContextLoader来完成真正的加载工作。只有当Groovy在classpath下的时候,才会被支持。
- WebDelegatingSmartContextLoader。另外一个默认加载器。和上面的类似,具体工作由AnnotationConfigWebContextLoader,GenericXmlWebContextLoader或GenericGroovyXmlWebContextLoader 实现。web ContextLoader 只有在测试类上添加了@WebAppConfiguration注解,才可以使用。只有当Groovy在classpath下的时候,才会被支持。
- AnnotationConfigContextLoader。从注解注释的类中加载标准ApplicationContext 。
- AnnotationConfigWebContextLoader。从注解注释的类中加载WebApplicationContext 。
- GenericGroovyXmlContextLoader。从配置文件加载标准ApplicationContext 。配置文件可以是Groovy 脚本或XML配置文件。
- GenericGroovyXmlWebContextLoader。从配置文件加载WebApplicationContext 。配置文件可以是Groovy 脚本或XML配置文件。
- GenericXmlContextLoader。从XML 配置加载标准ApplicationContext 。
- GenericXmlWebContextLoader。从XML 配置加载WebApplicationContext 。
- GenericPropertiesContextLoader。从Java 配置文件,加载标准ApplicationContext 。
Spring TestContext Framework内部的默认配置已经可以满足所有的一般情况。然而,有些开发团队或者第三方架构想更改默认的ContextLoader,实现自定义的ContextLoader或者ContextCache,比如将ContextCustomizerFactory 和TestExecutionListener 的自定义实现作为参数设置。类似于这种控制TestContext 框架的操作,Spring提供了加载策略。
TestContextBootstrapper 定义了引导TestContext 框架的SPI. TestContextBootstrapper 被TestContextManager 用来为测试加载TestExecutionListener 实现,以及创建TestContext。自定义引导策略可以通过@BootstrapWith配置到测试类上,直接使用和通过元注解使用都可以。如果一个引导程序没有通过@BootstrapWith配置,则会默认使用DefaultTestContextBootstrapper 或者WebTestContextBootstrapper。具体使用哪个,依赖于@WebAppConfiguration的配置。
由于TestContextBootstrapper SPI将会基于新的需求而改动,实现者最好不要直接实现接口,而是扩展AbstractTestContextBootstrapper或者它的子类。
spring提供了TestExecutionListener 的默认加载实现类,且按照以下的顺序加载:
- ServletTestExecutionListener。为Servlet API mocks 配置Servlet API mocks。
- DirtiesContextBeforeModesTestExecutionListener。为before方法处理@DirtiesContext注解。
- DependencyInjectionTestExecutionListener。为测试提供依赖注入。
- DirtiesContextTestExecutionListener。为after方法处理@DirtiesContext注解。
- TransactionalTestExecutionListener。提供事物的测试执行,默认回滚。
- SqlScriptsTestExecutionListener。执行通过@Sql配置的sql脚本
自定义TestExecutionListeners 可以通过 @TestExecutionListeners注册到测试类和父类中。可以阅读 annotation support和@TestExecutionListeners的javadoc获得详细信息。
在少量测试用例时,用户可以通过@TestExecutionListeners来注册自定义TestExecutionListeners;但是,在测试集中使用就会显得笨重。从Spring Framework 4.1开始, 通过SpringFactoriesLoader 机制实现的自动发现默认TestExecutionListener解决了这个问题。
spring-test 模块在org.springframework.test.context.TestExecutionListener包下定义了所有的默认核心TestExecutionListeners ,key存放在META-INF/spring.factories文件中。第三方框架和开发者可以在自己工程的META-INF/spring.factories文件中定义自定义TestExecutionListeners 作为默认监听器。
当TestContext框架通过SpringFactoriesLoader 机制发现默认TestExecutionListeners ,实例化的监听器将会被AnnotationAwareOrderComparator按照Ordered 接口或者@Order注解 进行排序。Spring提供的AbstractTestExecutionListener 和所有默认的TestExecutionListeners ,都实现了Order接口,并赋予了合适的顺序值。第三方架构和开发者也可以通过Order接口或者@Order为自定义的TestExecutionListeners 设置合适的顺序值。可以通过阅读javadoc获得默认TestExecutionListeners 的顺序值。
如果一个自定义的TestExecutionListener 通过@TestExecutionListeners注册,默认的监听器将不会被注册。在大多数场景下,这将有利于开发者除了自定义监听器之外,定义所有的监听器。下面是这种风格的配置示范:
@ContextConfiguration
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class
})
public class MyTest {
// class body...
}
这种方式对于开发者的挑战是,开发者需要明确知道所有默认加载的监听器。但是默认的监听器会在不同的Spring释放版本中发生变化,比如SqlScriptsTestExecutionListener 在4.1中引入,DirtiesContextBeforeModesTestExecutionListener 在4.2中引入。此外,第三方框架,如Spring Security注册自定义的TestExecutionListeners 。
为了避免开发者必须知道所有默认监听器,@TestExecutionListeners 的属性mergeMode 可以设置成MergeMode.MERGE_WITH_DEFAULTS。MERGE_WITH_DEFAULTS 表示本地声明的监听器将会和默认监听器合并。合并规则将冗余的监听器删除,通过AnnotationAwareOrderComparator 进行排序。如果一个监听器实现了Order接口或配置了@Order,则会影响它的排序位置;否则本地实现的监听器会附加到默认监听器后边。
比如说,MyCustomTestExecutionListener 类配置了顺序值500,比ServletTestExecutionListener 的顺序值1000少,MyCustomTestExecutionListener 将会排在ServletTestExecutionListener之前。先前的例子应该修改为:
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
public class MyTest {
// class body...
}
每个TestContext都为测试实例提供了上下文管理和缓存。测试实例不自动接收配置的ApplicationContext。然而如果一个测试类实现了ApplicationContextAware 接口,将为测试实例提供ApplicationContext 的引用。注意AbstractJUnit4SpringContextTests 和AbstractTestNGSpringContextTests 实现了ApplicationContextAware,因此可以自动接收到ApplicationContext 。
除了实现ApplicationContextAware 接口,你可以在通过属性或方法注解@Autowired来注入context 。比如:
@RunWith(SpringRunner.class)
@ContextConfiguration
public class MyTest {
@Autowired
private ApplicationContext applicationContext;
// class body...
}
相似的,如果你的测试用例配置为WebApplicationContext,你可以这么配置:
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class MyWebAppTest {
@Autowired
private WebApplicationContext wac;
// class body...
}
@Autowired 注入是通过默认监听器DependencyInjectionTestExecutionListener 实现的。Section 15.5.5, “Dependency injection of test fixtures”
使用TestContext 框架的测试用例不需要扩展任何特定类或实现接口来配置。只需要在类级别定义@ContextConfiguration就可以实现配置。如果你的测试用例没有声明资源locations或者注解类,ContextLoader 将会通过默认位置下的默认文件,或默认的配置类来加载上下文。除了资源location和注解的类,context也可以通过应用程序上下文initializers配置。
余下的部分将介绍如何通过@ContextConfiguration中描述的XML配置文件,Groovy脚本,注解(通常是@Configuration)类或者context initializers 来配置ApplicationContext。另外,你可以实现和配置自己的SmartContextLoader 。
为了使用XML配置文件来加载ApplicationContext ,需要在测试类上配置@ContextConfiguration,并在属性locations中配置XML配置文件的位置列表。一个直接文件或相对路径,比如locations,会被认为是定义测试类的classpath下的文件。如果一个路径以“/”开头,会被认为是绝对路径,比如”/org/example/config.xml”。还可以用资源URL的方式配置:classpath:, file:, http:开头。
@RunWith(SpringRunner.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"})
public class MyTest {
// class body...
}
@ContextConfiguration支持locations的别名value。因此,如果不需要配置其他的@ContextConfiguration属性,可以省去locations使用简洁的配置方式:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
public class MyTest {
// class body...
}
如果同时配置了@ContextConfiguration的locations 和value 属性,TestContext 会试着寻找默认XML资源位置。GenericXmlContextLoader 和GenericXmlWebContextLoader 会根据测试类的名字查找默认配置。如果你的测试类名字为com.example.MyTest,GenericXmlContextLoader 将会加载”classpath:com/example/MyTest-context.xml”。
package com.example;
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration
public class MyTest {
// class body...
}
通过Groovy脚本配加载ApplicationContext ,脚本需要使用Groovy Bean Definition DSL.和XML类似,需要使用@ContextConfiguration注释测试类,并通过属性locations或value 配置脚本位置。
如果classpath下有Groovy ,Spring TestContext框架会自动支持Groovy。
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})
public class MyTest {
// class body...
}
如果同时定义了locations和value属性,TestContext会加载默认配置文件。GenericGroovyXmlContextLoader 和GenericGroovyXmlWebContextLoader 会通过类的名字加载文件。比如你的类名为com.example.MyTest,则会加载”classpath:com/example/MyTestContext.groovy”。
package com.example;
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration
public class MyTest {
// class body...
}
XML和Groovy配置都可以同时设置@ContextConfiguration的locations和value属性。如果配置的文件以.xml结尾,则会使用XmlBeanDefinitionReader加载;否则将会使用GroovyBeanDefinitionReader。
下面的demo展示了怎样同时使用两者:
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
public class MyTest {
// class body...
}
使用注解来配置ApplicationContext ,在测试类上使用@ContextConfiguration,并通过classes 属性配置其他的注解配置的类。
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
// class body...
}
注解配置的类包含以下几种:
- @Configuration配置的类
- Spring的一个bean(@Component, @Service, @Repository等配置的类)
- JSR-330标准的类,通过javax.inject的注解配置
- 其他包含@Bean方法的类。
可以阅读@Configuration和@Bean的javadoc获得详细信息。尤其要关注@Bean
Lite Mode。
如果忽略了@ContextConfiguration的classes属性,TestContext框架将会试着加载默认配置类。AnnotationConfigContextLoader 和AnnotationConfigWebContextLoader 将会探测测试类中满足@Configuration要求的内部静态类。在下面的实例中,OrderServiceTest 测试类定义了一个内部静态类Config ,框架将会默认使用Config来加载ApplicationContext 。注意,配置类的名字是任意的。另外,一个测试类可以包含多个用于配置的内部静态测试类。
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
@ContextConfiguration
public class OrderServiceTest {
@Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
}
有些时候我们需要混合使用XML, Groovy scripts, annotated classes来加载ApplicationContext 。比如说,在生产环境使用XML,在测试环境中使用@Configuration配置测试专用的spring bean。反之亦然。
进一步来说,一些第三方测试框架(比如Spring Boot)对使用不同类型的配置文件(e.g., XML configuration files, Groovy scripts, and @Configuration classes)加载ApplicationContext 提供了一级支持。Spring Framework最开始不支持这种标准的部署。因此,spring-test中的大部分的SmartContextLoader 实现,在一个测试上下文中只支持一种配置方式;然而这并不是意味着你不能同时使用多个。GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader 同时支持XML 和Groovy脚本配置。进一步来说,第三方框架通过同时配置locations ,classes 实现混合使用的支持。如果使用TestContext,你有如下的选择:
如果想使用资源位置 (e.g., XML or Groovy)和@Configuration类进行配置,你可以使用一种作为切入,并包含或引入其他的配置。比如在XML或Groovy脚本中通过组件扫描或定义为普通bean的方式引入@Configuration类;反之,在@Configuration配置的类中,通过@ImportResource引入XML配置文件或Groovy脚本。注意,这和生产环境的配置支持是相似的:生产环境中可以定义一系列的XML和Groovy脚本文件,或@Configuration类,进行ApplicationContext 加载配置。当然,也可以include或import其他类型的配置文件。
可以通过@ContextConfiguration配置initializers来加载ApplicationContext 。@ContextConfiguration的属性initializers 可以配置ApplicationContextInitializer实现类的数组。配置的上下文initializers将会用来加载ConfigurableApplicationContext 。注意initializer 支持的ConfigurableApplicationContext 实例必须和SmartContextLoader 创建的ApplicationContext 类型相匹配(通常是GenericApplicationContext)。另外,initializers 的调用顺序,依赖于实现Spring接口Ordered 或者@Order注释,或者标准的@Priority注释提供的值。
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class)
public class MyTest {
// class body...
}
我们可以在@ContextConfiguration中完全忽略掉XML,Groovy和注解注释的类的配置,完全通过ApplicationContextInitializer 来加载和注册Bean。比如以编程方式从XML或配置类加载bean的定义。
@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class)
public class MyTest {
// class body...
}
@ContextConfiguration通过属性inheritLocations 和inheritInitializers 控制是否从父类继承资源文件、注解的类的配置和initializers 的配置。两个属性的默认值为true。也就是说测试类会继承相关的配置信息。明确的说,当前测试类对于资源文件,注解注释的类以及initializers的配置,会追加到父类的配置信息之后。因此,子类有是否继承相关配置的选项。
如果inheritLocations 和inheritInitializers 被设置成false,子类的locations、annotated classes和initializers配置将会替换父类的配置。
在下面实例中使用的XML locations配置,ExtendedTest 将会从”base-config.xml” 和”extended-config.xml”加载ApplicationContext ,并按照这个顺序加载。”extended-config.xml”中定义的bean将会覆盖 “base-config.xml”中的定义。
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml")
public 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")
public class ExtendedTest extends BaseTest {
// class body...
}
相似的,下面的实例使用了annotated classes。ExtendedConfig 会覆盖BaseConfig的bean定义。
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from BaseConfig
@ContextConfiguration(classes = BaseConfig.class)
public class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@ContextConfiguration(classes = ExtendedConfig.class)
public class ExtendedTest extends BaseTest {
// class body...
}
在下面的实例中,ExtendedTest 将会使用BaseInitializer 和and ExtendedInitializer加载ApplicationContext 。但是运行的顺序依赖于Ordered 接口,@Order或@Priority的顺序定义。
@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by BaseInitializer
@ContextConfiguration(initializers = BaseInitializer.class)
public class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@ContextConfiguration(initializers = ExtendedInitializer.class)
public class ExtendedTest extends BaseTest {
// class body...
}
Spring 3.1对于环境变量和profiles 提供了一级支持。集成测试可以配置成为不同的测试场景激活特定的bean定义。这需要在测试类上配置@ActiveProfiles,从而在加载ApplicationContext 的时候提供一组需要激活的profiles。
@ActiveProfiles可以和新的SmartContextLoader SPI的任意实现配合,但是@ActiveProfiles没有被老的ContextLoader SPI支持。
让我们看一下使用XML配置和@Configuration类的实例
<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>
package com.bank.service;
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
当TransferServiceTest 运行,他的ApplicationContext 将会通过classpath根目录下的app-config.xml配置加载。如果查看了app-config.xml将会发现bean accountRepository 依赖于dataSource;然而dataSource 没有作为顶级bean定义。相反,dataSource 被定义了三次:production Profile,dev profile以及默认profile。
TransferServiceTest 类注释了@ActiveProfiles(“dev”),Spring TestContext Framework只对profile为{“dev”}的内容加载到ApplicationContext 。因此,框架会加载一个填充了测试数据的嵌入式数据库,accountRepository bean会引用开发的DataSource。这就是我们在集成测试中需要的。
有的时候需要将bean划分到default profile。当没有配置profile 时,没有配置profile的bean才会被加载。这可以用于为应用程序的默认状态定义fallback bean。比如说,你可能为dev和production profile分别定义了数据源,当时当两者都没有定义时,定义一个内存数据库。
下面的代码列表展示了怎样实现相同的配置和集成测试,使用@Configuration类替代XML
@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();
}
}
package com.bank.service;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
在转变中,我们将XML配置拆分成四个独立的@Configuration 类:
- TransferServiceConfig:需要dataSource ,通过@Autowired注释
- StandaloneDataConfig:为嵌入式数据库定义了dataSource 。适用于开发者测试。
- JndiDataConfig:生产环境中,从JNDI 接受dataSource。
- DefaultDataConfig:当没有profile时,定义一个默认的嵌入式数据库dataSource 。
参考XML-based 的配置,我们依旧为TransferServiceTest添加注解@ActiveProfiles(“dev”)。但是这次我们为四个配置类都添加了@ContextConfiguration注解。测试类的主体没有变化。
在常理认为,一组profile可以在一个项目中的多个测试类中使用。因此为了避免@ActiveProfiles重复使用,我们在基类中配置一次,子类会默认继承基类的配置。在下面的列子中,@ActiveProfiles的配置迁移到抽象父类AbstractIntegrationTest。
package com.bank.service;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
public abstract class AbstractIntegrationTest {
}
package com.bank.service;
// "dev" profile inherited from superclass
public class TransferServiceTest extends AbstractIntegrationTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
@ActiveProfiles也支持属性inheritProfiles ,从而避免profile的继承。
package com.bank.service;
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
public class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
进一步说,profile可以用编程式测试替代声明式测试-例如,依赖于:
- 当前操作系统
- 是否在持续集成构建环境进行测试
- 确定的环境变量
- 自定义类级别注解
- 等等
为了实现编程式的bean定义,需要实现自定义的ActiveProfilesResolver 并将其设置到@ActiveProfiles的resolver 属性。下面的实例展示了怎样实现和注册自定义的OperatingSystemActiveProfilesResolver。可以通过javadoc获得进一步了解。
package com.bank.service;
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
public class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
package com.bank.service.test;
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
String[] resolve(Class> testClass) {
String profile = ...;
// determine the value of profile based on the operating system
return new String[] {profile};
}
}
SPring 3.1就在框架级别对环境中配置属性源层级结构的一级支持,直到Spring4.1集成测试可以使用测试专用的属性源配置测试用例。与使用在@Configuration类的注解@PropertySource形成鲜明对比,@TestPropertySource可以用于测试类上,为测试属性文件或内置属性声明资源位置。这些测试属性源将会在Environment 中被添加到PropertySources ,从而作用于ApplicationContext 加载。
@TestPropertySource可以和SmartContextLoader SPI的任意实现配合使用,但是老版本的ContextLoader SPI不支持@TestPropertySource。
SmartContextLoader 的实现类通过MergedContextConfiguration的getPropertySourceLocations()和getPropertySourceProperties()两个方法获取合并后的属性源值。
声明测试属性源
测试属性文件可以通过@TestPropertySource的locations和value属性配置,这些将会在之后的实例中展示。
传统的和XML格式的文件都被支持,比如”classpath:/com/example/test.properties”或”file:///path/to/file.xml”。
每个路径都被spring认为是一个Resource。一个直接的path-比如”test.properties”-会被认为是classpath资源,定位到测试类的包路径对应的文件夹路径下。如果路径以斜杠开头,则认为是绝对路径,比如”/org/example/test.xml”。如果路径是URL(以classpath:, file:, http:等开头),则会采用对应的协议加载。不支持资源location通配符(比如*/.properties):每个location都必须指向一个.properties或.xml资源。
@ContextConfiguration
@TestPropertySource("/test.properties")
public class MyIntegrationTests {
// class body...
}
键值对形式的内置属性可以通过@TestPropertySource 的properties属性配置,如下面的例子。所有的键值对将会作为一个单独的测试PropertySource 添加到Environment 中,且此属性有最高优先级。
键值对支持的语法和java properties文件中的语法相同:
- “key=value”
- “key:value”
- “key value”
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"})
public class MyIntegrationTests {
// class body...
}
默认properties文件探测
如果@TestPropertySource没有任何配置(没有指定locations和properties的值),框架将会自动探测配置了注解类的默认属性文件。比如说,一个配置了注解的测试类com.example.MyTest,对应的默认属性文件为”classpath:com/example/MyTest.properties”。如果找不到默认文件,那么将会抛出异常IllegalStateException 。
优先级
和操作系统环境变量、java系统配置以及通过@PropertySource或编程添加的属性相比,测试属性源具有更高的优先级。因此测试属性源可以用来选择性的覆盖系统或应用程序中的属性。进一步说,内置属性和资源位置加载的属性相比,具有更高优先级。
在下面的实例中,timezone和port属性和其他定义在”/test.properties”中的属性一样,将会覆盖在系统或应用程序中定义的具有相同名字的属性源。进一步说,如果”/test.properties”定义了timezone和port,那么将会被properties属性定义的属性覆盖。
@ContextConfiguration
@TestPropertySource(
locations = "/test.properties",
properties = {"timezone = GMT", "port: 4242"}
)
public class MyIntegrationTests {
// class body...
}
继承和覆盖测试属性
@TestPropertySource支持inheritLocations和inheritProperties属性,表示是否继承父类的资源文件位置配置和内置属性配置。默认的值未true。这意味着测试类会继承locations和内置属性的配置。明确的说,测试类的locations 和内置属性配置会附加到父类的属性配置后。因此,子类可以选择是否扩展locations和内置属性。注意,之后配置的属性会覆盖之前相同名称的配置。另外,之前描述的优先级策略也适用于测试属性源。
如果@TestPropertySource的inheritLocations和inheritProperties属性设置成false,测试类的配置将会替代父类的配置定义。
在下面的例子中,BaseTest的ApplicationContext将会只使用“base.properties”文件加载。相反,ExtendedTest将会使用”base.propertires“和”extended.properties“两个文件加载。
@TestPropertySource("base.properties")
@ContextConfiguration
public class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
// ...
}
在下面的例子中,BaseTest将会使用内置的Key1属性配置。相对应的,ExtendedTest将会使用内置的key1和key2属性配置。
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
public class BaseTest {
// ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
// ...
}
Spring3.2开始支持集成测试加载WebApplicationContext。为了引导TestContext加载WebApplicationContext而不是加载ApplicationContext,需要在测试类上配置@WebAPPConfiguration。
测试类上配置的@WebAPPConfiguration将会告知TestContext框架(TestContext framework (TCF))应该加载WebApplicationContext(WAC) 。TCF在后台确认MockServletContext被创建,并提供给测试的WAC。默认下,MockServletContext的base路径为 “src/main/webapp”。这可以理解为你的JVM为根(通常是项目的路径)的一个相对路径。如果熟悉maven工程的web应用的目录结构,你就会知道”src/main/webapp”是WAR的默认根路径。如果希望使用classpath来指向资源根路径,可以以classpath:开头。
注意,Spring测试对于WebApplicationContext的支持实在标准的ApplicationContext基础上实现的。你可以使用XML配置文件,Groovy脚本或@Configuration,@ContextConfiguration注解实现配置。当然也可以自由的使用任何测试注解,比如@ActiveProfiles, @TestExecutionListeners, @Sql, @Rollback等等。
下面的demo演示了各种方法加载WebApplicationContext。
约定
@RunWith(SpringRunner.class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in same package
// or static nested @Configuration class
@ContextConfiguration
public class WacTests {
//...
}
上面的demo延时了TestContext框架的约定配置。如果@WebAppConfiguration没有配置任何资源路径,默认的资源路径为”file:src/main/webapp”。相似的,如果@ContextConfiguration没有任何资源locations,注解注释的类,或者initalizers配置,Spring会按照约定来探测配置。(WacTests包路径相同路径下的 “WacTests-context.xml”,或者静态内置@Configuration配置的类)
默认资源语法
@RunWith(SpringRunner.class)
// file system resource
@WebAppConfiguration("webapp")
// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
public class WacTests {
//...
}
实例展示了怎样通过@WebAPPConfiguration配置资源根路径,且通过@ContextConfiguration配置XML资源位置。要记住很重要的一点:两个注解的路径配置语法是不同的。默认情况,@WebAPPConfiguration的资源路径是文件系统路径;然而@ContextConfiguration是classpath路径。
明确资源的语法
@RunWith(SpringRunner.class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
public class WacTests {
//...
}
在第三个例子中,两个注解中配置的信息,会覆盖默认资源信息。
为了提供广泛的web测试,Spring3.2提供了ServletTestExecutionListener,并在默认情况下是激活的。当进行WebAPPlicationContext测试时,TestExecutionListener通过Spring Web的RequestContextHolder 在每个测试方法之前建立了默认的thread-local 状态,同时也通过@WebAppConfiguration的配置,创建了一个MockHttpServletRequest, MockHttpServletResponse, 和 ServletWebRequest。ServletTestExecutionListener同时确保MOckHttpServletResponse和ServletWebRequest会注入到测试实例中,并在测试完成的时候清空thread-local状态。
一旦测试加载了WebApplicationContext,你会发现需要和web mock互动-比如,在web组件调用后构建测试点或执行assert。下面的例子演示了哪些mock可以autowired注入到测试实例中。注意WebApplicationContext和MockServletContext都是测试集缓存的;然而其他的mock是每个测试方法通过ServletTestExecutionListener管理自己的。
* 注入mocks*
@WebAppConfiguration
@ContextConfiguration
public class WacTests {
@Autowired
WebApplicationContext wac; // cached
@Autowired
MockServletContext servletContext; // cached
@Autowired
MockHttpSession session;
@Autowired
MockHttpServletRequest request;
@Autowired
MockHttpServletResponse response;
@Autowired
ServletWebRequest webRequest;
//...
}
一旦TestContext框架为一个测试加载了一个ApplicationContext或WebApplicationContext,这个context会被缓存,并在此测试集中的相同配置unique的测试中重用。为了理解缓存是怎样工作的,我们需要理解unique和test suite。
一个ApplicationContext可以使用配置参数结合而成的信息唯一标识。因此,可以通过配置参数结合而生成的key来缓存context。TestContext架构通过下面的参数来构建缓存key:
- locations (from @ContextConfiguration)
- classes (from @ContextConfiguration)
- contextInitializerClasses (from @ContextConfiguration)
- contextCustomizers (from ContextCustomizerFactory)
- contextLoader (from @ContextConfiguration)
- parent (from @ContextHierarchy)
- activeProfiles (from @ActiveProfiles)
- propertySourceLocations (from @TestPropertySource)
- propertySourceProperties (from @TestPropertySource)
- resourceBasePath (from @WebAppConfiguration)
举个例子,如果TestClassA的注解@ContextConfiguration设定locations={“app-config.xml”, “test-config.xml”},TestContext框架将会加载对应的ApplicationContext并将context存储到静态context缓存中,key是依据这些地址生成。如果TestClassB也定义了locations={“app-config.xml”, “test-config.xml”},但是可没有定义为@WebAppConfiguration(那么将会使用不同的ContextLoader,活跃的profiles,context initializers,测试属性源以及上级context),所以两个测试用例会使用相同的ApplicationContext。这意味着只会构建一次应用程序的context(每个测试集中),测试执行会更快。
Spring TestContext架构采用静态cache存储应用程序context。这意味着context存储在一个静态变量中。换句话说,如果测试在独立的进程中运行,静态cache会在各个test执行期间清空,这会导致缓存机制失效。
为了使用缓存机制,所有的测试必须在同一个进程,或测试集中。这会通过IDE中将所有的测试作为一个组来实现。相似的,当测试用例通过构建框架执行,比如ant,maven,gradle,我们需要确认框架不会再测试之间执行fork。比如,如果maven surefire插件的forkMode设置成always或pertest,TestContext测试框架将不能再测试类之间缓存应用程序context,构建过程将会变得很慢。
自从Spring Framework 4.3开始,context缓存的默认最大值为32.当缓存的数量达到最大值,框架将会执行LRU策略清除数据并关闭context。可以通过命令行或者构建脚本设置最大值,更改JVM系统属性spring.test.context.cache.maxSize。另外一种方式为,通过SpringProperties API设置此属性的值。
由于在一个测试集中记载大量的应用程序context,会导致测试集花费不必要的长时间来执行。通常知道有多少个context被加载和缓存是十分必要的。为了查看context缓存的统计信息,可以将org.springframework.test.context.cache的日志级别设成DEBUG。
在一些不常见的场景中,测试污染了应用程序context,需要重新加载-比如修改了一个bean的定义或一个应用程序对象,你可以通过@DirtiesContext配置你的测试类或测试方法。(Section 15.4.1, “Spring Testing Annotations”中有详细介绍)。这会使Spring从缓存中删除Context,并在下一个test执行之前重新构建应用程序Context。注意,DirtiesContextBeforeModesTestExecutionListener 实现了对于注解@DirtiesContext的支持,且此listener是默认激活的。
当写依赖于Spring ApplicationContext的集成测试,一般情况下一个context已经完全可以满足测试;但是在有些时候为测试维护一个ApplicationContext层级是必要的。比如,如果正在开发一个Spring MVC应用程序,一般需要通过Spring的ContextLoaderListener加载一个根WebApplicationContext,通过Spring的DispatcherServlet加载一个子WebApplicationContext。这会产生一个父子层级结构,结构中共享的组件和基础配置在root context定义,而将web特制的组件定义到子context中。我们可以在Spring Batch的应用程序中找到另一个例子:parent context提供了共享批量架构的配置,子context为特定的批处理任务提供配置。
自从Spring Framework 3.2.2开始,在写集成测试的时候,我们可以通过@ContextHierarchy定义context的层级,@ContextHierarchy可以用于单独的测试类或者在一个测试类层级中。如果一个context层级在拥有多个类的测试类层级中定义,那么这个context层级可以合并或覆盖已有层级中的context。当为层级中的指定一级合并配置,配置资源的类型必须一致的;否则,采用不同资源类型将会认为是不同层级。
下面这个使用Junit4的例子说明了需要context层级的集成测试的流行配置语法。
ControllerIntegrationTests 是Spring MVC应用程序一种典型的集成测试语法,定义了两层的context:一层是root WebApplicationContext(使用TestAppConfig @Configuration类),另一层是dispatcher servlet WebApplicationContext (使用WebConfig @Configuration类加载)。autowired到测试实例的是子WebApplicationContext 中的一个。(最低层级的context)
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
public class ControllerIntegrationTests {
@Autowired
private WebApplicationContext wac;
// ...
}
下面的测试类,在一个测试类层级中定义了context。AbstractWebTests 在Spring的web 应用程序中声明了根WebApplicationContext 的配置。注意,AbstractWebTests 没有声明@ContextHierarchy;因此,AbstractWebTests的子类可以加入到一个层级context中,或者仅仅按照@ContextConfiguration标准语法配置。SoapWebServiceTests 和RestWebServiceTests 都是从AbstractWebTests 扩展,并通过@ContextConfiguration定义context层级。结果就是三个应用程序context将会被加载(每个都@ContextConfiguration),AbstractWebTests 配置的context将会作为其他子类加载的context的parent。
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml")
public class SoapWebServiceTests extends AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")
public class RestWebServiceTests extends AbstractWebTests {}
下面的类展示了怎样使用命名的层级,从而将context插入到context层级的指定层。BaseTests 定义了两级:parent和child。ExtendedTests扩展了BaseTests,并应道SPring TestContext Framework为child层级合并配置信息。@ContextConfiguration中name属性都设成“child”,从而确认合并的层级。三个应用程序的contex会被加载:一个是”/app-config.xml”, 一个是”/user-config.xml”, 一个是 {“/user-config.xml”, “/order-config.xml”}。参考之前的实例,通过”/app-config.xml”加载的应用程序Context将作为”/user-config.xml”和{“/user-config.xml”, “/order-config.xml”}的parent。
@RunWith(SpringRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
public class ExtendedTests extends BaseTests {}
和上一个例子相反,这个例子演示了怎样通过@ContextConfiguration的inheritLocations= false来实现覆盖给定名字的context层级。ExtendedTests的应用程序context只会从”/test-user-config.xml”加载,他的父节点将会通过”/app-config.xml”加载。
@RunWith(SpringRunner.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(
name = "child",
locations = "/test-user-config.xml",
inheritLocations = false
))
public class ExtendedTests extends BaseTests {}
如果@DritiesContext配置的测试的context是一个层级的一部分,那么可以使用hierarchyMode可以用来控制context缓存的清空规则。可以通过 Spring Testing Annotations和@DirtiesContext的javadoc获得更多信息。