领先一步
VMware 提供培训和认证,以加快您的进度。
了解更多正如 Jürgen Höller 在他的文章中提到的那样,该文章宣布了Spring 3.1 M2 的发布,Spring TestContext 框架(*) 进行了全面修改,以便为@Configuration
类和环境配置文件提供一流的测试支持。
在这篇文章中,我将首先引导您完成一些演示这些新测试功能的示例。然后,我将介绍 TestContext 框架中的一些新扩展点,这些扩展点使这些新功能成为可能。
请注意:这是我公司博客www.swiftmind.com的交叉发布。
TestContext 框架的核心是允许您使用@ContextConfiguration
注解测试类,以指定用于为测试加载ApplicationContext
的配置文件。默认情况下,ApplicationContext
使用GenericXmlContextLoader
加载,该加载器从 XML Spring 配置文件加载上下文。然后,您可以通过使用@Autowired
、@Resource
或@Inject
注解测试类中的字段来访问ApplicationContext
中的 bean。
Spring 3.0 通过@Configuration
类引入了对基于 Java 的配置的支持,但是 TestContext 框架直到现在还没有提供合适的ContextLoader
来支持测试中的@Configuration
类。Spring 3.1 M2 为此目的引入了一个新的AnnotationConfigContextLoader
,并且@ContextConfiguration
注解已更新为通过新的classes
属性支持声明@Configuration
类。
现在让我们来看一些示例。
Spring 参考手册的测试章节提供了许多关于如何使用 XML 配置文件配置集成测试的示例,但我们将在此处包含一个示例作为快速介绍。
如果您已经熟悉 Spring TestContext 框架,可以跳到下一节。
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<!-- this bean will be injected into the OrderServiceTest class -->
<bean id="orderService" class="com.example.OrderServiceImpl">
<!-- set properties, etc. -->
</bean>
<!-- other beans -->
</beans>
package com.example;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "classpath:/com/example/OrderServiceTest-context.xml"
@ContextConfiguration
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
}
在前面的示例中,我们配置 JUnit 使用SpringJUnit4ClassRunner
运行我们的测试。我们使用 JUnit 的@RunWith
注解来实现这一点。我们还使用 Spring 的@ContextConfiguration
注解注释我们的测试类,而无需指定任何属性。在这种情况下,将使用默认的GenericXmlContextLoader
,并遵循 *约定优于配置* 的原则,Spring 将从classpath:/com/example/OrderServiceTest-context.xml
加载我们的ApplicationContext
。在testOrderService()
方法中,我们可以直接测试使用@Autowired
注入到测试实例中的OrderService
。请注意,orderService
在OrderServiceTest-context.xml
中定义为一个 bean。
Spring 3.1 M2 对使用@Configuration
类进行集成测试的支持类似于上面的基于 XML 的示例。因此,让我们重新编写该示例以使用@Configuration
类和新的AnnotationConfigContextLoader
。
package com.example;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the static inner ContextConfiguration class
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {
@Configuration
static class ContextConfiguration {
// 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 的示例之间存在一些显着差异
ContextConfiguration
类中的@Configuration
和@Bean
从 XML 转换为 Java。AnnotationConfigContextLoader
已通过@ContextConfiguration
的loader
属性指定。否则,测试的配置和实现保持不变。
那么,Spring 如何知道使用静态内部ContextConfiguration
类来加载ApplicationContext
呢?答案是 *约定优于配置*。默认情况下,如果没有显式声明类,AnnotationConfigContextLoader
将查找测试类的名为ContextConfiguration
的静态内部类。根据@Configuration
类的要求,此静态内部类必须是非 final 的且非私有的。
注意:从 Spring 3.1 M2 开始,默认配置类必须准确命名为ContextConfiguration
。但是,从 Spring 3.1 RC1 开始,命名限制已被取消。换句话说,从 RC1 开始,您可以选择将默认配置类命名为您想要的任何名称,但其他要求仍然适用。
在下面的示例中,我们将看到如何声明显式配置类。
package com.example;
@Configuration
public class OrderServiceConfig {
// this bean will be injected into the OrderServiceTest class
@Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
package com.example;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the OrderServiceConfig class
@ContextConfiguration(classes=OrderServiceConfig.class, loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
}
我们现在已将静态内部ContextConfiguration
类提取到名为OrderServiceConfig
的顶级类中。为了指示AnnotationConfigContextLoader
使用此配置类而不是依赖于默认配置类,我们只需通过@ContextConfiguration
的新classes
属性声明OrderServiceConfig.class
。与@ContextConfiguration
的locations
属性(用于资源位置)一样,我们可以通过向classes
属性提供一个 Class[] 数组来声明多个配置类——例如:@ContextConfiguration(classes={Config1.class, Config2.class}, ... )
。
这就结束了对使用@Configuration
类进行集成测试的介绍。现在让我们来看看 Spring 对环境配置文件的测试支持。
正如 Chris Beams 在他关于Spring 3.1 M1 发布的发布公告以及他的后续博客@Profile 的介绍中所讨论的那样,Spring 3.1 在框架中引入了一流的支持,用于环境和配置文件(也称为 *bean 定义配置文件*)的概念。从 Spring 3.1 M2 开始,集成测试也可以配置为激活特定 bean 定义配置文件以用于各种测试场景。这是通过使用新的@ActiveProfiles
注解注释测试类并提供应在加载测试的ApplicationContext
时激活的配置文件列表来实现的。
注意:@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>
package com.bank.service;
@RunWith(SpringJUnit4ClassRunner.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
将从类路径根目录中的app-config.xml
配置文件加载。如果您检查app-config.xml
,您会注意到accountRepository
bean 依赖于dataSource
bean;但是,dataSource
未定义为顶级 bean。相反,dataSource
定义了两次:一次在 *生产* 配置文件中,一次在 *开发* 配置文件中。
通过使用@ActiveProfiles("dev")
注解TransferServiceTest
,我们指示 Spring TestContext 框架加载ApplicationContext
,并将活动配置文件设置为 {"dev"}。结果,将创建一个嵌入式数据库,并且accountRepository
bean 将与开发 DataSource 的引用连接起来。这很可能是我们在集成测试中想要的!
以下代码清单演示了如何实现相同的配置和集成测试,但使用@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
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@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(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class,
classes={TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
在此变体中,我们将 XML 配置拆分为三个独立的@Configuration
类
TransferServiceConfig
:通过使用@Autowired
进行依赖注入来获取dataSource
StandaloneDataConfig
:为适合开发人员测试的嵌入式数据库定义一个dataSource
JndiDataConfig
:定义一个从 JNDI 在生产环境中检索的dataSource
与基于 XML 的配置示例一样,我们仍然使用@ActiveProfiles("dev")
注解TransferServiceTest
,但是这次我们通过@ContextConfiguration
注解指定AnnotationConfigContextLoader
和所有三个配置类。测试类本身的主体保持完全不变。
有关如何简化上述@Configuration
类的详细信息,请参阅Spring 3.1 M1:介绍 @Profile博客文章。
从 Spring 2.5 开始,*Spring TestContext 框架*已缓存集成测试的ApplicationContexts
,其基于为给定测试生成的键,该键是从所有合并的上下文资源位置生成的。由于ContextLoader
SPI 只支持位置,因此此密钥生成算法足以唯一标识用于加载ApplicationContext
的配置。但是,随着对配置类和配置文件的支持增加,旧算法已不再足够。
因此,Spring 3.1 M2 更新了上下文缓存键生成算法,其中包含以下所有内容:
@ContextConfiguration
)@ContextConfiguration
)@ContextConfiguration
)@ActiveProfiles
)这对开发者意味着您可以实现一个声明特定资源位置或配置类的基测试类。然后,如果您想针对该基配置运行测试,但使用不同的活动配置文件,您可以扩展该基测试类,并使用@ActiveProfiles
注释每个具体子类,为每个子类提供一组不同的配置文件进行激活。因此,每个子类都将定义一组唯一的配置属性,这将导致加载和缓存不同的ApplicationContexts
。
正如本文前面提到的,Spring 3.1 M2 引入了一个新的SmartContextLoader
SPI,它取代了现有的ContextLoader
SPI。如果您计划开发或已经开发了自己的自定义ContextLoader
,您可能需要仔细查看新的SmartContextLoader
接口。与旧的ContextLoader
接口相比,SmartContextLoader
可以处理资源位置和配置类。此外,SmartContextLoader
可以在其加载的上下文中设置活动的bean定义配置文件。
ContextLoader
将继续得到支持,任何现有的该SPI实现都应该继续按原样工作;但是,如果您想在自定义加载器中支持配置类或环境配置文件,则需要实现SmartContextLoader
。
如果您一直密切关注到目前为止提供的示例,您可能已经注意到,当使用配置类时,我们总是必须为@ContextConfiguration
的loader
属性显式声明AnnotationConfigContextLoader.class
。但是,当我们指定XML配置文件(或依赖于约定优于配置)时,GenericXmlContextLoader
将默认使用。
如果Spring能够注意到我们使用的是配置类还是XML资源位置,然后自动选择合适的ContextLoader
来加载我们的应用程序上下文,岂不是很好?
是的,我们也这么认为!;)
因此,对于Spring 3.1 RC1,我们计划引入一个DelegatingSmartContextLoader
,它将委托给候选SmartContextLoaders
列表(即GenericXmlContextLoader
和AnnotationConfigContextLoader
),以确定哪个上下文加载器适合给定测试类的配置。然后将使用获胜的候选者来实际加载上下文。
这项工作完成后,DelegatingSmartContextLoader
将取代GenericXmlContextLoader
成为默认加载器。您可以随意关注JIRA中此项开发的进度:SPR-8387。
Spring 3.1 为@Configuration
类和环境配置文件提供了一流的测试支持,我们鼓励您尽快尝试这些功能。M2 是3.1 版本发布系列中的最后一个里程碑。因此,如果您发现任何错误或有任何改进建议,现在是采取行动的时候了!
(*) 参考手册尚未更新以反映对@Configuration
类和环境配置文件的测试支持,但这些功能肯定会在Spring 3.1 RC1或GA中得到很好的记录。与此同时,每个新类和注释的JavaDoc可以作为良好的起点。