抢先一步
VMware 提供培训和认证,助您加速进步。
了解更多正如 Jürgen Höller 在宣布 Spring 3.1 M2 发布的帖子中提到的那样,Spring TestContext Framework(*) 已经过全面改进,为 @Configuration
类和环境 Profile 提供了第一等的测试支持。
在本文中,我将首先向您介绍一些示例,演示这些新的测试特性。然后,我将介绍 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 Framework,请随意跳到下一节。
<?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 和非 private 的。
注意:从 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 对环境 profile 的测试支持。
正如 Chris Beams 在 Spring 3.1 M1 的发布公告及其后续博客 Introducing @Profile 中讨论的那样,Spring 3.1 在框架中引入了对环境和 Profile(又称 bean 定义 Profile)概念的一流支持。从 Spring 3.1 M2 开始,还可以配置集成测试,以针对各种测试场景激活特定的 bean 定义 Profile。这可以通过使用新的 @ActiveProfiles
注解来注解测试类,并提供在为测试加载 ApplicationContext
时应激活的 Profile 列表来实现。
注意:@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
定义了两次:一次在 production Profile 中,一次在 dev Profile 中。
通过使用 @ActiveProfiles("dev")
注解 TransferServiceTest
,我们指示 Spring TestContext Framework 加载 ApplicationContext
,并将其活动 Profile 设置为 {"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: Introducing @Profile 博客文章。
自 Spring 2.5 以来,Spring TestContext Framework 根据为给定测试合并的所有上下文资源位置生成的 key 来缓存集成测试的 ApplicationContexts
。由于 ContextLoader
SPI 只支持 locations,这个 key 生成算法足以唯一标识用于加载 ApplicationContext
的配置。然而,随着对配置类和 Profile 的支持增加,旧的算法已不再适用。
因此,Spring 3.1 M2 中更新了上下文缓存 key 生成算法,以包含以下所有内容:
@ContextConfiguration
)@ContextConfiguration
)@ContextConfiguration
)@ActiveProfiles
)作为开发者,这意味着您可以实现一个基础测试类,该类声明一组特定的资源位置或配置类。然后,如果您想针对该基础配置运行测试,但使用不同的活动 Profile,您可以扩展该基础测试类,并使用 @ActiveProfiles
注解每个具体的子类,为每个子类提供一组不同的 Profile 来激活。因此,这些子类中的每一个都将定义一组唯一的配置属性,这将导致加载和缓存不同的 ApplicationContexts
。
正如本文前面提到的,Spring 3.1 M2 引入了一个新的 SmartContextLoader
SPI,它取代了现有的 ContextLoader
SPI。如果您计划开发或已经开发了自己的自定义 ContextLoader
,您可能需要仔细研究新的 SmartContextLoader
接口。与旧的 ContextLoader
接口相比,SmartContextLoader
可以处理资源位置和配置类。此外,SmartContextLoader
可以在其加载的上下文中设置活动的 bean 定义 Profile。
ContextLoader
将继续得到支持,并且该 SPI 的任何现有实现都应继续按原样工作;但是,如果您想在自定义加载器中支持配置类或环境 Profile,您将需要实现 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
类和环境 Profile 提供了第一等的测试支持,我们鼓励您尽快试用这些功能。M2 是 3.1 发布列车中的最后一个里程碑。因此,如果您发现任何错误或有任何改进建议,现在就是采取行动的时候了!
(*) 参考手册尚未更新以反映对 @Configuration
类和环境 Profile 的测试支持,但这些功能肯定会在 Spring 3.1 RC1 或 GA 版本发布时得到完善的文档。在此期间,新类和注解的 JavaDoc 可以作为一个很好的起点。