领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多正如Juergen Hoeller在其宣布Spring Framework 3.2 RC1 发布的文章中提到的那样,Spring团队在测试支持方面引入了一些令人兴奋的新特性。最重要的是,我们添加了对测试Web应用程序的一流支持。[1]
在这篇文章中,我们将首先了解Spring Framework中一些新的通用测试特性,然后我们将详细介绍使用WebApplicationContext
以及请求和会话作用域bean进行测试的支持。最后,我们将探讨对ApplicationContextInitializers
的支持,并简要讨论应用程序上下文层次结构测试的路线图。
Rossen Stoyanchev稍后将发表一篇关于新的Spring MVC Test框架的详细文章,该框架为测试Spring MVC应用程序提供了第一流的支持。所以请务必继续关注,因为它基于本文后面讨论的基本Web测试支持。
spring-test
模块现在构建并支持JUnit 4.10和TestNG 6.5.2,并且spring-test
现在依赖于junit:junit-dep
Maven构件而不是junit:junit
,这意味着您可以完全控制对Hamcrest库(例如,hamcrest-core
,hamcrest-all
等)的依赖。
泛型工厂方法是使用Java泛型实现工厂方法设计模式的方法。以下是一些泛型工厂方法的示例签名
public static <T> T mock(Class<T> clazz) { ... }
public static <T> T proxy(T obj) { ... }
在Spring配置中使用泛型工厂方法决不是特定于测试的,但是诸如EasyMock.createMock(MyService.class)
或Mockito.mock(MyService.class)
之类的泛型工厂方法通常用于在测试应用程序上下文中创建Spring bean的动态模拟。例如,在Spring Framework 3.2之前,以下配置可能无法将OrderRepository
自动装配到OrderService
中。原因是,根据应用程序上下文中bean初始化的顺序,Spring可能会将orderRepository
bean的类型推断为java.lang.Object
而不是com.example.repository.OrderRepository
。
<beans>
<!-- OrderService is autowired with OrderRepository -->
<context:component-scan base-package="com.example.service"/>
<bean id="orderRepository" class="org.easymock.EasyMock"
factory-method="createMock"
c:_="com.example.repository.OrderRepository" />
</beans>
在Spring 3.2中,现在可以正确推断工厂方法的泛型返回类型,并且模拟对象的按类型自动装配应该按预期工作。因此,自定义变通方法(例如MockitoFactoryBean
,EasyMockFactoryBean
或Springockito)可能不再需要。
我们引入了MockEnvironment
,它补充了现有的MockPropertySource
,以完成对Spring 3.1中引入的环境和属性源抽象的模拟支持。
关于Web组件的单元测试支持,我们为现有的Servlet API模拟对象(如MockServletContext
,MockHttpSession
,MockFilterChain
和MockRequestDispatcher
)添加了新特性,并且我们引入了与REST Web服务相关的新模拟对象:客户端的MockClientHttpRequest
和MockClientHttpResponse
以及服务器端的MockHttpInputMessage
和MockHttpOutputMessage
。
在Spring 3.2中,我们弃用了SimpleJdbcTestUtils
,转而使用改进的JdbcTestUtils
类,该类除了提供SimpleJdbcTestUtils
以前提供的所有功能外,还提供了新的countRowsInTableWhere()
和dropTables()
实用程序方法。这些更改有助于避免与使用已弃用的SimpleJdbcTemplate
相关的编译器警告,并提供了一种方便的方法来使用WHERE
子句计算表中的行数以及删除表列表。
如果您熟悉Spring TestContext Framework中对事务集成测试的支持,那么您可能知道用于测试的事务管理器必须按照约定命名为“transactionManager”。从Spring 2.5开始,这可以通过@TransactionConfiguration
批注来覆盖(例如,@TransactionConfiguration(transactionManager="txMgr")
);但是,如果应用程序上下文中只有一个PlatformTransactionManger
,则不再需要使用此批注。换句话说,只要上下文中只定义了一个事务管理器,就不再需要限定该事务管理器的名称:如果只有一个,TestContext框架将直接使用它。
Spring 3.1引入了TransactionManagementConfigurer
接口,用于在结合使用@Configuration
类和@EnableTransactionManagement
时(即,与使用XML配置和<tx:annotation-driven />
相比),以编程方式指定要与@Transactional
方法一起使用的事务管理器。因此,从Spring 3.2开始,如果您的一个组件(即,通常是@Configuration
类)实现了TransactionManagementConfigurer
,则TestContext框架将使用该组件指定的事务管理器。
本文的其余部分明确地讨论了Spring TestContext框架中的新特性。如果您已经熟悉TestContext框架,可以跳到下一节。否则,您可能需要首先熟悉以下段落中提供的链接信息。
在Spring 2.5中,我们引入了Spring TestContext Framework,它提供了可与JUnit或TestNG一起使用的注释驱动的集成测试支持。本文中的示例将重点关注基于JUnit的测试,但此处使用的所有特性也适用于TestNG。
在Spring 3.1中,我们修改了Spring TestContext Framework,增加了对使用@Configuration
类和环境配置文件进行测试的支持。
WebApplicationContext
?@WebAppConfiguration
注释您的测试类。这就是全部。您的测试类上存在@WebAppConfiguration
会指示TestContext框架(TCF)为您的集成测试加载WebApplicationContext
(WAC)。在后台,TCF确保创建MockServletContext
并将其提供给测试的WAC。默认情况下,MockServletContext
的基本资源路径将设置为“src/main/webapp”。这被解释为相对于JVM根目录的路径(即,通常是您项目的路径)。如果您熟悉Maven项目中Web应用程序的目录结构,您就会知道“src/main/webapp”是WAR根目录的默认位置。如果需要覆盖此默认值,只需向@WebAppConfiguration
注释提供替代路径(例如,@WebAppConfiguration("src/test/webapp")
)。如果希望从类路径而不是文件系统引用基本资源路径,只需使用Spring的classpath:前缀。
请注意,Spring对WebApplicationContexts
的测试支持与其对标准ApplicationContexts
的支持相当。使用WebApplicationContext
进行测试时,您可以通过@ContextConfiguration
声明XML配置文件或@Configuration
类。当然,您也可以自由地使用任何其他测试注释,例如@TestExecutionListeners
,@TransactionConfiguration
,@ActiveProfiles
等。
让我们来看一些例子……
约定
@RunWith(SpringJUnit4ClassRunner.class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in same package
// or static nested @Configuration class
@ContextConfiguration
public class WacTests {
//...
}
上面的示例演示了TestContext框架对约定优于配置的支持。如果您使用@WebAppConfiguration
注释测试类而不指定资源基路径,则资源路径将有效地默认为“file:src/main/webapp”。类似地,如果您声明@ContextConfiguration
而不指定资源locations
、注释的classes
或上下文initializers
,Spring将尝试使用约定(即,WacTests
类所在的包中的“WacTests-context.xml”或静态嵌套的@Configuration
类)来检测您的配置是否存在。
默认资源语义
@RunWith(SpringJUnit4ClassRunner.class)
// file system resource
@WebAppConfiguration("webapp")
// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
public class WacTests {
//...
}
本示例演示如何使用@WebAppConfiguration
显式声明资源基路径,以及使用@ContextConfiguration
声明 XML 资源位置。需要注意的是,这两个注解的路径语义不同。默认情况下,@WebAppConfiguration
资源路径基于文件系统;而@ContextConfiguration
资源位置基于类路径。
显式资源语义
@RunWith(SpringJUnit4ClassRunner.class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
// file system resource
@ContextConfiguration(
"file:src/main/webapp/WEB-INF/servlet-config.xml")
public class WacTests {
//...
}
在第三个示例中,我们看到可以通过指定 Spring 资源前缀来覆盖这两个注解的默认资源语义。将本示例中的注释与前一个示例进行对比。
为了提供全面的 Web 测试支持,Spring 3.2 引入了一个新的ServletTestExecutionListener
,它默认启用。当针对WebApplicationContext
进行测试时,这个TestExecutionListener 通过 Spring Web 的RequestContextHolder
在每个测试方法之前设置默认的线程局部状态,并基于通过@WebAppConfiguration
配置的基资源路径创建MockHttpServletRequest
、MockHttpServletResponse
和ServletWebRequest
。ServletTestExecutionListener
还确保可以将MockHttpServletResponse
和ServletWebRequest
注入到测试实例中,并在测试完成后清理线程局部状态。
一旦为测试加载了WebApplicationContext
,您可能会发现需要与 Web 模拟交互——例如,设置测试装置或在调用 Web 组件后执行断言。以下示例演示了哪些模拟可以自动注入到您的测试实例中。请注意,WebApplicationContext
和MockServletContext
在整个测试套件中都被缓存;而其他模拟则由ServletTestExecutionListener
按测试方法管理。
注入模拟对象
@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;
//...
}
请求和会话范围的 Bean 已被 Spring 支持多年,但测试它们一直有点复杂。从 Spring 3.2 开始,通过以下步骤测试请求范围和会话范围的 Bean 变得轻而易举。
@WebAppConfiguration
注解测试类,确保为您的测试加载了WebApplicationContext
。WebApplicationContext
(即通过依赖注入)中检索到的 Web 组件。以下代码片段显示了登录用例的 XML 配置。请注意,userService
Bean 依赖于请求范围的loginAction
Bean。此外,LoginAction
使用SpEL 表达式实例化,该表达式从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们将希望通过 TestContext 框架管理的模拟对象来配置这些请求参数。
请求范围 Bean 配置
<beans>
<bean id="userService"
class="com.example.SimpleUserService"
c:loginAction-ref="loginAction" />
<bean id="loginAction" class="com.example.LoginAction"
c:username="#{request.getParameter('user')}"
c:password="#{request.getParameter('pswd')}"
scope="request">
<aop:scoped-proxy />
</bean>
</beans>
在RequestScopedBeanTests
中,我们将UserService
(即被测对象)和MockHttpServletRequest
都注入到我们的测试实例中。在我们的requestScope()
测试方法中,我们通过在提供的MockHttpServletRequest
中设置请求参数来设置测试装置。当在我们的userService
上调用loginUser()
方法时,我们可以确保用户服务可以访问当前MockHttpServletRequest
(即我们刚刚设置参数的那个)的请求范围的loginAction
。然后,我们可以根据用户名和密码的已知输入对结果执行断言。
请求范围 Bean 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RequestScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpServletRequest request;
@Test
public void requestScope() {
request.setParameter("user", "enigma");
request.setParameter("pswd", "$pr!ng");
LoginResults results = userService.loginUser();
// assert results
}
}
以下代码片段类似于我们上面看到的请求范围 Bean 的代码片段;但是,这次userService
Bean 依赖于会话范围的userPreferences
Bean。请注意,UserPreferences
Bean 使用 SpEL 表达式实例化,该表达式从当前 HTTP 会话中检索主题。在我们的测试中,我们需要在 TestContext 框架管理的模拟会话中配置一个主题。
会话范围 Bean 配置
<beans>
<bean id="userService"
class="com.example.SimpleUserService"
c:userPreferences-ref="userPreferences" />
<bean id="userPreferences"
class="com.example.UserPreferences"
c:theme="#{session.getAttribute('theme')}"
scope="session">
<aop:scoped-proxy />
</bean>
</beans>
在SessionScopedBeanTests
中,我们将UserService
和MockHttpSession
注入到我们的测试实例中。在我们的sessionScope()
测试方法中,我们通过在提供的MockHttpSession
中设置预期的“theme”属性来设置测试装置。当在我们的userService
上调用processUserPreferences()
方法时,我们可以确保用户服务可以访问当前MockHttpSession
的会话范围的userPreferences
,并且我们可以根据配置的主题对结果执行断言。
会话范围 Bean 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SessionScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpSession session;
@Test
public void sessionScope() throws Exception {
session.setAttribute("theme", "blue");
Results results = userService.processUserPreferences();
// assert results
}
}
Spring 3.1 引入了ApplicationContextInitializer
接口,允许对ConfigurableApplicationContext
进行编程初始化——例如,向 Spring Environment
抽象注册属性源或激活 Bean 定义配置文件。可以通过为ContextLoaderListener
指定contextInitializerClasses
(通过context-param
)以及为DispatcherServlet
指定init-param
(通过init-param
)在web.xml
中配置初始化器。
要在集成测试中使用上下文初始化器,只需通过 Spring 3.2 中引入的新initializers
属性在@ContextConfiguration
中声明初始化器类即可。可以通过inheritInitializers
属性控制跨测试类层次结构的初始化器的继承,该属性默认为true
。由于ApplicationContextInitializer
提供了一种完全编程的方法来初始化应用程序上下文,因此初始化器可以选择配置整个上下文。换句话说,如果已声明初始化器,则在通过@ContextConfiguration
配置的集成测试中不再绝对需要 XML 资源位置或带注解的类。最后但并非最不重要的一点是,上下文初始化器是根据 Spring 的Ordered
接口或@Order
注解进行排序的。
以下代码示例演示了在集成测试中使用上下文初始化器的各种方法。第一个示例展示了如何将单个初始化器与 XML 资源位置结合使用。下一个示例声明多个上下文初始化器。第三个清单演示了在类层次结构中使用初始化器,其中在ExtendedTest
中声明的上下文初始化器列表将与在BaseTest
中声明的上下文初始化器列表合并。回想一下,初始化器的调用顺序会根据 Spring 的Ordered
接口的实现或@Order
注解的存在而受到影响。第四个示例与第三个示例相同,只是@ContextConfiguration
中的inheritInitializers
属性已设置为false
。结果是将忽略(即覆盖)父类中声明的任何上下文初始化器。最后的清单演示了如何仅从上下文初始化器加载ApplicationContext
,而无需声明 XML 资源位置或带注解的类。
单个初始化器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations = "/app-config.xml",
initializers = CustomInitializer.class)
public class ApplicationContextInitializerTests {}
多个初始化器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations = "/app-config.xml",
initializers = {
PropertySourceInitializer.class,
ProfileInitializer.class
})
public class ApplicationContextInitializerTests {}
合并的初始化器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = BaseConfig.class,
initializers = BaseInitializer.class)
public class BaseTest {}
@ContextConfiguration(
classes = ExtendedConfig.class,
initializers = ExtendedInitializer.class)
public class ExtendedTest extends BaseTest {}
被覆盖的初始化器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = BaseConfig.class,
initializers = BaseInitializer.class)
public class BaseTest {}
@ContextConfiguration(
classes = ExtendedConfig.class,
initializers = ExtendedInitializer.class,
inheritInitializers = false)
public class ExtendedTest extends BaseTest {}
没有资源的初始化器
// does not declare 'locations' or 'classes'
@ContextConfiguration(
initializers = EntireAppInitializer.class)
public class InitializerWithoutConfigFilesOrClassesTest {}
一旦 TestContext 框架为测试加载了ApplicationContext
,该上下文将被缓存并在同一测试套件中声明相同唯一上下文配置的所有后续测试中重用。这里需要注意的是,ApplicationContext
通过其上下文缓存键(即用于加载它的配置参数的组合)唯一标识。
从 Spring 3.2 开始,ApplicationContextInitializer
类也包含在上下文缓存键中。此外,如果上下文是WebApplicationContext
,则其基资源路径(通过@WebAppConfiguration
定义)也将包含在上下文缓存键中。有关缓存的更多详细信息,请参阅参考手册的上下文缓存部分。
注意:截至 Spring Framework 3.2 RC1,尚未实现对上下文层次结构的支持。
在由 Spring TestContext 框架管理的集成测试中,当前仅支持扁平的、非层次的上下文。换句话说,没有简单的方法为测试创建具有父子关系的上下文。但是上下文层次结构在生产部署中受支持。因此,能够测试它们将会很好。
考虑到这一点,Spring 团队希望引入集成测试支持,以加载具有父上下文的测试应用程序上下文,理想情况下,将支持以下常见的层次结构。
WebApplicationContext
← 分发器WebApplicationContext
WebApplicationContext
← 分发器WebApplicationContext
当前提案包括引入一个新的@ContextHierarchy
注解,该注解将包含嵌套的@ContextConfiguration
声明,以及@ContextConfiguration
中一个新的name
属性,该属性可用于合并或覆盖上下文层次结构中的命名配置。
为了阐明该提案,让我们来看几个示例……
AppCtxHierarchyTests
演示了在单个测试类中声明的父子上下文层次结构,其中上下文是标准上下文(即非 Web 上下文)。
具有上下文层次结构的单个测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration("parent.xml"),
@ContextConfiguration("child.xml")
})
public class AppCtxHierarchyTests {}
ControllerIntegrationTests
演示了在单个测试类中声明的父子上下文层次结构,其中上下文是WebApplicationContexts
,并模拟了典型的 Spring MVC 部署。
根 WAC 和分发器 WAC
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(
name = "root",
classes = WebAppConfig.class),
@ContextConfiguration(
name = "dispatcher",
locations = "/spring/dispatcher-config.xml")
})
public class ControllerIntegrationTests {}
以下代码清单演示了如何在测试类层次结构中构建上下文层次结构,其中测试类层次结构中的每一层负责配置上下文层次结构中的自身层级。在这两个子类的测试中执行测试将导致加载(和缓存)三个应用程序上下文和两个不同的上下文层次结构。
类和上下文层次结构
@RunWith(SpringJUnit4ClassRunner.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{}
如果您有兴趣了解有关上下文层次结构提案的更多信息,或者想参与讨论,请随时关注以下 JIRA 问题并向我们提供您的反馈。
Spring Framework 3.2 引入了多个新的测试特性,重点关注对 Web 应用程序测试的一流支持。我们鼓励您尽快尝试这些特性并给我们反馈。此外,敬请关注 Rossen Stoyanchev 关于新的 Spring MVC Test 框架的后续文章。如果您发现任何错误或有任何改进建议,现在是 采取行动 的时候了!
[1] 参考手册尚未更新以反映对 Web 应用程序的测试支持,但这些特性将在 Spring 3.2 GA 中得到充分的文档记录。