Spring Framework 3.2 RC1:新的测试特性

工程 | Sam Brannen | 2012年11月7日 | ...

正如Juergen Hoeller在其宣布Spring Framework 3.2 RC1 发布的文章中提到的那样,Spring团队在测试支持方面引入了一些令人兴奋的新特性。最重要的是,我们添加了对测试Web应用程序的一流支持。[1]

           请注意:这是来自我的Swiftmind公司博客的交叉发布

在这篇文章中,我们将首先了解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-corehamcrest-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中,现在可以正确推断工厂方法的泛型返回类型,并且模拟对象的按类型自动装配应该按预期工作。因此,自定义变通方法(例如MockitoFactoryBeanEasyMockFactoryBeanSpringockito)可能不再需要。

模拟对象

我们引入了MockEnvironment,它补充了现有的MockPropertySource,以完成对Spring 3.1中引入的环境和属性源抽象的模拟支持。

关于Web组件的单元测试支持,我们为现有的Servlet API模拟对象(如MockServletContextMockHttpSessionMockFilterChainMockRequestDispatcher)添加了新特性,并且我们引入了与REST Web服务相关的新模拟对象:客户端的MockClientHttpRequestMockClientHttpResponse以及服务器端的MockHttpInputMessageMockHttpOutputMessage

JDBC测试支持

在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框架


本文的其余部分明确地讨论了Spring TestContext框架中的新特性。如果您已经熟悉TestContext框架,可以跳到下一节。否则,您可能需要首先熟悉以下段落中提供的链接信息。

在Spring 2.5中,我们引入了Spring TestContext Framework,它提供了可与JUnit或TestNG一起使用的注释驱动的集成测试支持。本文中的示例将重点关注基于JUnit的测试,但此处使用的所有特性也适用于TestNG。

在Spring 3.1中,我们修改了Spring TestContext Framework,增加了对使用@Configuration类和环境配置文件进行测试的支持。



加载WebApplicationContext


  • 问题:如何告诉TestContext框架加载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 模拟


为了提供全面的 Web 测试支持,Spring 3.2 引入了一个新的ServletTestExecutionListener,它默认启用。当针对WebApplicationContext进行测试时,这个TestExecutionListener 通过 Spring Web 的RequestContextHolder在每个测试方法之前设置默认的线程局部状态,并基于通过@WebAppConfiguration配置的基资源路径创建MockHttpServletRequestMockHttpServletResponseServletWebRequestServletTestExecutionListener还确保可以将MockHttpServletResponseServletWebRequest注入到测试实例中,并在测试完成后清理线程局部状态。

一旦为测试加载了WebApplicationContext,您可能会发现需要与 Web 模拟交互——例如,设置测试装置或在调用 Web 组件后执行断言。以下示例演示了哪些模拟可以自动注入到您的测试实例中。请注意,WebApplicationContextMockServletContext在整个测试套件中都被缓存;而其他模拟则由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


请求和会话范围的 Bean 已被 Spring 支持多年,但测试它们一直有点复杂。从 Spring 3.2 开始,通过以下步骤测试请求范围和会话范围的 Bean 变得轻而易举。

  1. 通过使用@WebAppConfiguration注解测试类,确保为您的测试加载了WebApplicationContext
  2. 将模拟请求或会话注入到您的测试实例中,并根据需要准备测试装置。
  3. 调用您从配置的WebApplicationContext(即通过依赖注入)中检索到的 Web 组件。
  4. 对模拟对象执行断言。

以下代码片段显示了登录用例的 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中,我们将UserServiceMockHttpSession注入到我们的测试实例中。在我们的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
  • EAR ← 根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 问题并向我们提供您的反馈。

  • SPR-5613:上下文层次结构支持
  • SPR-9863:Web 上下文层次结构支持


总结


Spring Framework 3.2 引入了多个新的测试特性,重点关注对 Web 应用程序测试的一流支持。我们鼓励您尽快尝试这些特性并给我们反馈。此外,敬请关注 Rossen Stoyanchev 关于新的 Spring MVC Test 框架的后续文章。如果您发现任何错误或有任何改进建议,现在是 采取行动 的时候了!



[1] 参考手册尚未更新以反映对 Web 应用程序的测试支持,但这些特性将在 Spring 3.2 GA 中得到充分的文档记录。



获取 Spring 新闻通讯

关注 Spring 新闻通讯

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部