8.3. 集成测试

Spring Framework

8.3. 集成测试

8.3.1. 概览

能够无需部署到你的应用服务器上或连接其它企业架构就实现集成测试是非常重要的。这可以让你来进行以下测试:

  • 正确配置Spring IoC 容器上下文。

  • 使用JDBC或ORM工具的数据访问。可能包括如SQL脚本,Hibernate query,JPA 实体映射等的正确性验证。

Spring框架提供集成测试的一流支持,相关类打包在spring-test.jar类库中。在这个类库中,你可以找到org.springframework.test包,有很多方便使用Spring容器进行集成测试的类,而且同时不依赖应用服务器或其它部署环境。这些测试会比单元测试慢,但会比Cactus(译者注:Apache测试服务端Java代码的工具http://jakarta.apache.org/cactus/index.html )测试或依靠部署到一个应用服务器上来进行远程测试要快捷的多。

在2.5版本之前,Spring已经提供了面向JUnit 3.8的单元测试支持. 在2.5版本中, Spring 提供了单元和集成测试支持 Spring TestContext框架。 它是实际测试框架的混合体,因此能够帮助在多个测试环境包括JUnit 3.8,JUnit 4.4, TestNG等中进行测试。 注意Spring TestContext框架需要Java 5+支持.

8.3.2. 使用哪个支持框架

Spring团队推荐使用Spring TestContext框架 来进行所有新的单元测试和集成测试,以包括ApplicationContext或需要事务管理的情况。 但如果你开发在Java5之前的环境上,就需要继续使用JUnit 3.8遗留支持. 另外,显式 JPA集成测试支持 依赖于shadow class载入来进行JPA类测试(class instrumentation)目前只能与JUnit 3.8遗留支持相容。 如果你要测试的JPA提供者不需要class instrumentation,就推荐使用TestContext框架。

8.3.3. 通用目标

Spring集成测试支持框架提供了一些通用目标,包括:

下面的章节具体描述每一个目标并提供指向特定支持框架的信息的链接。

8.3.3.1. 上下文管理及缓存

Spring集成测试支持框架提供了ApplicationContext的持久化载入和这些上下文的缓存机制。 对已载入上下文的缓存是很重要的,因为如果你是在一个大型的项目中,启动时间会成为一个问题——不是因为Spring本身的开销, 而是因为靠Spring容器来初始化的对象需要很长时间。比如一个有50-100 Hibernate映射文件的项目可能需要10-20秒来载入映射文件, 而每次单一测试fixture的每个单一测试前都要这样的时间开销,减慢了整体的测试进度进而降低效率。

测试类通常会提供一个数组来包含XML配置元数据的资源路径——通常是classpath——来配置应用。这通常和web.xml或其它部署描述中指定的配置路径是相同或相近的。

默认情况下,一旦载入,ApplicationContext将在每次测试中重用。 这样启动的开销将只需要一次(每个测试fixture),接下来的测试执行就会快得多。 在一些少见的会“污染”应用上下文的案例中需要重新载入—— 例如,改变一个bean定义或应用对象的状态—— Spring的测试支持提供了在执行下一个测试前让测试fixture重新载入配置并重建应用上下文的机制。

上下文管理和缓存使用:

8.3.3.2. 测试fixtures依赖注入

当Spring集成测试支持框架载入你的应用上下文时,它们能通过依赖注入选择性配置测试类实例。 这提供了一个方便的机制来使用预先在应用上下文中配置的bean来搭建测试fixture。 很大的好处就是你可以在各种测试场景中重用应用上下文(例如配置Spring管理的对象图, 事务代理DataSource等),从而能避免为单个的测试案例重复进行测试fixture搭建。

作为例子,考虑一个场景:我们有一个HibernateTitleDao类来实现数据访问逻辑,假设是Title域对象。我们希望编写测试所有以下方面的集成测试:

  • Spring配置: 最基本的,是否所有与HibernateTitleDao bean相关的配置都是正确和存在的?

  • Hibernate映射配置文件: 是否所有映射都是正确的并且lazy-loading设置也到位了?

  • HibernateTitleDao逻辑:是否类的已配置示例的实现与预期相同?

测试fixtures依赖注入使用:

8.3.3.3. 事务管理

访问实际数据库的测试的一个通常问题是对持久化状态的影响。 即使你使用开发数据库,状态的改变也可能影响后面的测试。而且很多操作 —— 如插入或修改持久化数据 —— 不能在事务外完成(或验证)。

Spring集成测试支持框架满足了这些需求。默认情况下,对每次测试它们会创建并回滚事务。 你编写代码可以假定事务已经存在。如果你在测试中调用事务代理对象,它们将根据配置的事务语义正常响应。 另外,如果测试方法在事务内删除了选定表的数据,这个事务会默认回滚,数据库也将回到测试执行前的状态。 事务支持通过在测试应用上下文中定义的PlatformTransactionManager bean提供。

如果你希望事务被提交 —— 不常见,但可能你希望特定的测试插入或修改数据库 —— Spring集成测试支持框架 可以通过调用一个继承下来的钩子(Hook)方法或声明特定注解来让事务提交而不是回滚。

事务管理使用:

8.3.3.4. 集成测试支持类

Spring集成测试支持框架提供了几个abstract支持类来简化编写集成测试。 这些测试基类提供了定义良好的测试框架钩子,比如方便的变量实例和方法,来访问以下对象:

  • ApplicationContext: 用来进行显式bean查找或整体测试上下文状态。

  • JdbcTemplateSimpleJdbcTemplate: 用来查询并确认状态。 例如,你可能需要在创建对象并通过ORM工具持久化到数据库中的测试案例运行前后进行查询,以确认数据在数据库中存在了。 (Spring将确保查询在同一个事务范围内运行。) 你需要通知ORM工具来'flush'变化以确保正常工作, 例如使用Hibernate Session接口的flush()方法。

你经常会提供一个应用范围的超类来为多个集成测试提供有用的实例变量。

支持类:

8.3.4. JDBC测试支持

org.springframework.test.jdbc包含有SimpleJdbcTestUtils类,它 是一个基于Java5的JDBC相关工具方法集,用来简化标准数据库测试场景。注意AbstractTransactionalJUnit38SpringContextTests, AbstractTransactionalJUnit4SpringContextTests, 和AbstractTransactionalTestNGSpringContextTests 提供了简便的方法来内部代理到SimpleJdbcTestUtils

8.3.5. 常用注解

Spring框架在org.springframework.test.annotation 包中提供了常用的Spring特定的注解集,如果你在Java5或以上版本开发,可以在测试中使用它。

  • @IfProfileValue

    提示一下,注解测试只针对特定的测试环境。 如果配置的ProfileValueSource类返回对应的提供者的名称, 这个测试就可以启动。这个注解可以应用到一个类或者单独的方法。

    @IfProfileValue(name="java.vendor", value="Sun Microsystems Inc.")
    public void testProcessWhichRunsOnlyOnSunJvm() {
        // some logic that should run only on Java VMs from Sun Microsystems
    }

    同时@IfProfileValue可配置一个列表 (使用OR 语义) 来在JUnit环境中获得TestNG的测试组支持。 看下面的例子:

    @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
    public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
        // some logic that should run only for unit and integration test groups
    }
  • @ProfileValueSourceConfiguration

    类级别注解用来指定当通过@IfProfileValue注解获取已配置的profile值时使用何种ProfileValueSource。 如果@ProfileValueSourceConfiguration没有在测试中声明,将默认使用SystemProfileValueSource

    @ProfileValueSourceConfiguration(CustomProfileValueSource.class)
    public class CustomProfileValueSourceTests {
        // class body...
    }
  • @DirtiesContext

    在测试方法上出现这个注解时,表明底层Spring容器在该方法的执行中被“污染”,从而必须在方法执行结束后重新创建(无论该测试是否通过)。

    @DirtiesContext
    public void testProcessWhichDirtiesAppCtx() {
        // some logic that results in the Spring container being dirtied
    }
  • @ExpectedException

    表明被注解方法预期在执行中抛出一个异常。预期异常的类型在注解中给定。如果该异常的实例在测试方法执行中被抛出, 则测试通过。同样的如果该异常实例没有在测试方法执行时抛出,则测试失败。

    @ExpectedException(SomeBusinessException.class)
    public void testProcessRainyDayScenario() {
        // some logic that should result in an Exception being thrown
    }
  • @Timed

    表明被注解的测试方法必须在规定的时间区间内执行完成(以毫秒记)。如果测试执行时间超过了规定的时间区间,测试就失败了。

    注意该时间区间包括测试方法本身的执行,任何重复测试(参见 @Repeat),还有任何测试fixture的set uptear down时间。

    @Timed(millis=1000)
    public void testProcessWithOneSecondTimeout() {
        // some logic that should not take longer than 1 second to execute
    }
  • @Repeat

    表明被注解的测试方法必须重复执行。执行的次数在注解中声明。

    注意重复执行范围包括包括测试方法本身的执行,以及任何测试fixture的set uptear down

    @Repeat(10)
    public void testProcessRepeatedly() {
        // ...
    }
  • @Rollback

    表明被注解方法的事务在完成后是否需要被回滚。 如果true,事务将被回滚,否则事务将被提交。 使用@Rollback接口来在类级别覆写配置的默认回滚标志。

    @Rollback(false)
    public void testProcessWithoutRollback() {
        // ...
    }
  • @NotTransactional

    出现该注解表明测试方法必须在事务中执行。

    @NotTransactional 
    public void testProcessWithoutTransaction() {
        // ...
    }

注解支持:

  • JUnit 3.8遗留支持: 所有上面列举的注解都被支持,但必须AbstractAnnotationAwareTransactionalTests类联合使用,以保证这些注解能起作用。

  • TestContext框架: 支持上面列举的所有注解,而且提供了额外的TestContext特定注解 (例如@ContextConfiguration@BeforeTransaction等等)。 注意,但是一些注解只有与JUnit联合使用时(例如,基于SpringJUnit4ClassRunnerJUnit 3.8以及JUnit 4.4的测试类)。 详细内容参见TestContext框架章节。

8.3.6. JUnit 3.8遗留支持

Spring JUnit 3.8 遗留支持类打包在org.springframework.test包中。 这个包提供了有用的JUnit TestCase超类, 扩展它可以在容器外集成测试中引入Spring ApplicationContext类或在测试方法级别获得事务支持。

8.3.6.1. 上下文管理及缓存

AbstractSingleSpringContextTests为基于JUnit 3.8的测试案例提供了上下文管理和缓存支持。 它暴露了一个protected方法来给子类覆写以提供上下文定义文件的路径:

protected String[] getConfigLocations()

这个方法的实现必须提供包含XML配置元数据的资源路径 —— 通常是类路径 —— 的一个数组。 这和在web.xml或其它部署配置中的资源路径是相同的或基本相同的。 作为可选方案,你也可以覆写下面的方法。详细内容参见相关JavaDoc。

protected String[] getConfigPaths()
protected String getConfigPath()

默认情况下,一旦配置文件被载入就会在每个测试案例中重用。 这样构建的开销只会产生一次(每个测试fixture),然后后面的测试执行会快速的多。 在较少的情况下测试可能“污染”应用上下文,需要重新载入 —— 例如, 改变一个bean定义或应用对象状态 —— 你可以调用AbstractSingleSpringContextTests类中的 setDirty()方法来让测试fixture在执行下一个测试案例时重新载 AbstractAnnotationAwareTransactionalTests类, 你可以使用@DirtiesContext来对测试方法进行注解以达到同样的效果。

8.3.6.2. 测试fixture依赖注入

AbstractDependencyInjectionSpringContextTests类(及其子类)载入你的应用上下文时, 它们可以通过Setter注入选择性配置你的测试类实例。你需要做的仅仅是定义实例变量和相应的setter方法。 AbstractDependencyInjectionSpringContextTests将在getConfigLocations()方法定义的配置文件集中自动查找相应对象。

假定这样一个场景,我们有一个HibernateTitleDao类(在通常目标章节详述)。 让我们看基于JUnit 3.8 的测试类实现本身(我们很快将看看配置本身)。

public final class HibernateTitleDaoTests extends AbstractDependencyInjectionSpringContextTests  {

    // this instance will be (automatically) dependency injected    
    private HibernateTitleDao titleDao;

    // a setter method to enable DI of the 'titleDao' instance variable
    public void setTitleDao(HibernateTitleDao titleDao) {
        this.titleDao = titleDao;
    }

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected String[] getConfigLocations() {
        return new String[] { "classpath:com/foo/daos.xml" };
    }

}

这个文件被getConfigLocations()方法指定(比如,"classpath:com/foo/daos.xml") 像这样:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- this bean will be injected into the HibernateTitleDaoTests class -->
    <bean id="titleDao" class="com.foo.dao.hibernate.HibernateTitleDao">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <!-- dependencies elided for clarity -->
    </bean>

</beans>

AbstractDependencyInjectionSpringContextTests类使用按类型自动装配。 因此如果你有多个bean定义是相同的类型,就不能在这些bean中使用这种方法。 这种情况下,你可以使用继承的applicationContext实例变量并实现显式的查找(比如), 调用applicationContext.getBean("titleDao")方法。

如果你不希望在测试案例中使用依赖注入,只要不声明任何public setter方法就可以简单实现。 作为替代的,你可以扩展AbstractSpringContextTests - 在org.springframework.test 包中的JUnit 3.8集成测试支持类层次的根 - 它仅仅包含了一些载入Spring上下文的简单方法,而且不在测试fixture中使用依赖注入。

8.3.6.2.1. 字段级别(Field Level)注入

如果不管何种原因,你的测试fixture中没有setter方法,Spring可以对protected字段进行依赖注入。 下面是前面使用字段级注入示例的新版本(Spring XML文件无需改变,仅仅需要改变测试fixture)。

public final class HibernateTitleDaoTests extends AbstractDependencyInjectionSpringContextTests  {

    public HibernateTitleDaoTests() {
        // switch on field level injection
        setPopulateProtectedVariables(true);
    }

    // this instance will be (automatically) dependency injected
    protected HibernateTitleDao titleDao;

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }

    // specifies the Spring configuration to load for this test fixture
    protected String[] getConfigLocations() {
        return new String[] { "classpath:com/foo/daos.xml" };
    }

}

在字段注入的情况下,不能使用自动装配:protected 实例变量被作为已配置的Spring容器的bean 查找名。

8.3.6.3. 事务管理

AbstractTransactionalSpringContextTests类 依赖于应用上下文中定义的PlatformTransactionManager bean。 名字是无关紧要的,因为使用了按类型自动装配.

通常你会扩展其子类AbstractTransactionalDataSourceSpringContextTests。 这个类也需要在应用上下文中有一个DataSource bean定义(同样可以是任意名称)。 它创建一个JdbcTemplate实例变量,可以用来方便的查询和删除选定表的内容( 请记住默认情况下事务将被回滚,因而这样做是安全的)。

如果你希望编程式提交事务 —— 不常见但对于特殊的插入数据库的测试很有用 —— 你可以调用继承自AbstractTransactionalSpringContextTests类的setComplete() 方法。 这将使事务提交而不是回滚。作为可替代的,如果你在Java 5或更高环境中开发扩展AbstractAnnotationAwareTransactionalTests类, 你可以使用@Rollback(false)来注解测试方法,以通过配置获得相同的效果。

通过调用endTransaction()方法,这里可以在测试案例完成时中止一个事务。 默认将回滚事务,除非前面调用了setComplete()方法。 这个特性当你希望测试‘断连接’的 数据对象行为是很有用,比如事务外的web或远程使用的Hibernate映射实体。 通常懒加载错误只有通过UI测试发现。如果你调用endTransaction()方法 可以保证JUnit测试时UI操作的正确性。

8.3.6.4. JUnit 3.8 遗留支持类

当你扩展 AbstractTransactionalDataSourceSpringContextTests类时,你将需要访问下面protected 实例变量:

  • applicationContext(ConfigurableApplicationContext): 继承自AbstractSingleSpringContextTests类。使用它可以进行显式bean查找或测试整个的上下文状态。

  • jdbcTemplate: 继承自AbstractTransactionalDataSourceSpringContextTests类,用于查询已确认状态。 例如,应用代码要创建一个对象,然后使用ORM工具将其持久化,这时你想在测试代码执行前后对其进行查询,以确定数据是否插入到数据库中(Spring会保证该查询运行在相同事务内)。你需要告诉你的ORM工具‘清空’其改变以正确完成任务,例如,使用HibernateSession接口的flush()方法。

8.3.6.5. Java 5+ 专有支持

8.3.6.5.1. 使用注解的事务相关测试

在上述常用注解之外, org.springframework.test.annotation包也有一个抽象 JUnit TestCase类来提供注解驱动的集成测试支持。

AbstractAnnotationAwareTransactionalTests类扩展了AbstractTransactionalDataSourceSpringContextTests类, 并通过扩展fixture引入一些(Spring专有)的注解。AbstractAnnotationAwareTransactionalTests 支持所有常用注解章节中列举的注解, 而且包括Spring的@Transactional注解,以显式配置事务语义。

8.3.6.5.2. JPA支持类

org.springframework.test.jpa包提供了基于Java 持久化API(JPA)的测试支持类。

  • AbstractJpaTests是一个方便的JPA相关测试的支持类, 它提供了和AbstractTransactionalDataSourceSpringContextTests相同的功能和即使在进行JPA规范需要的性能测试时也相同的性能。 它暴露了一个EntityManagerFactory接口和一个共享的EntityManager接口。 需要注入一个EntityManagerFactory接口, 以及通过超类获得DataSource接口和JpaTransactionManager接口。

  • AbstractAspectjJpaTests类是AbstractJpaTests的子类, 它激活了AspectJ 的装载期织入并能够让AspectJ指定一个自定义的aop.xml文件路径。

8.3.7. Spring TestContext Framework

Spring TestContext Framework (在org.springframework.test.context包中) 提供了一般的、注解驱动的单元和集成测试支持,它对使用的测试框架不做要求,可以使用JUnit 3.8、JUnit 4.4, TestNG 5.5等等。 TestContext框架也强调了约定优于配置的重要性,它提供了合理的默认值,同时也可以通过基于注解的配置进行改写。

除了一般的测试基础设施外,TestContext框架还以抽象支持类的形式对JUnit 3.8、JUnit 4.4和TestNG 5.5提供了显式的支持。 针对JUnit 4.4,该框架还提供了一个自定义的Runner,这使得用户无需继承特定的类就可以编写测试类了。

以下章节给出了TestContext框架的内部概览。 如果你仅仅关注如何使用该框架而不是使用你自己的监听器去扩展它,那么请直接跳到配置(上下文管理和缓存依赖注入事务管理)、 支持类注解支持章节。

8.3.7.1. 主要的抽象

框架的核心包括TestContextTestContextManager类以及TestExecutionListener接口。 每次测试都会创建TestContextManagerTestContextManager管理了一个TestContext, 它负责持有当前测试的上下文。TestContextManager还负责在测试执行过程中更新TestContext的状态并代理到TestExecutionListener, 它用来监测测试实际的执行(如提供依赖注入、管理事务等等)。请查看JavaDoc及Spring测试套件以获得进一步的信息和各种配置示例。

  • TestContext:封装测试执行的上下文,与当前使用的测试框架无关。

  • TestContextManagerSpring TestContext Framework的主入口点, 负责管理单独的TestContext并在定义好的执行点上向所有注册的TestExecutionListener发出事件通知: 测试实例的准备,先于特定的测试框架的前置方法,迟于后置方法

  • TestExecutionListener:定义了一个监听器API与TestContextManager发布的测试执行事件进行交互, 而该监听器就是注册到这个TestContextManager上的。

    Spring提供了TestExecutionListener的三个实现, 他们都是使用默认值进行配置的(通过@TestExecutionListeners注解): DependencyInjectionTestExecutionListenerDirtiesContextTestExecutionListenerTransactionalTestExecutionListener, 他们对测试实例提供了依赖注入支持,处理@DirtiesContext注解,并分别使用默认的回滚语义对测试提供事务支持。

以下三个章节讲述了如何通过注解配置TestContext框架并提供了使用该框架编写真实的单元和集成测试的示例。

8.3.7.2. 上下文管理和缓存

每个TestContext都会为其所负责的测试实例提供上下文和缓存管理。 测试实例不会自动访问配置好的ApplicationContext;然而,如果一个测试类实现了ApplicationContextAware接口, 那么测试实例就会拥有一个对ApplicationContext的引用(假如默认已经配置好了DependencyInjectionTestExecutionListener)。 AbstractJUnit38SpringContextTestsAbstractJUnit4SpringContextTestsAbstractTestNGSpringContextTests已经实现了ApplicationContextAware, 因此自带了上述功能。

与JUnit 3.8遗留支持不同,使用TestContext框架的测试类无需重写任何protected成员方法来配置应用上下文。 只需在类层次上声明@ContextConfiguration注解就可以完成配置。 如果你的测试类没有显式声明任何应用上下文资源的位置,那么配置好的ContextLoader就会决定如何以及是否从默认的集合位置上加载一个上下文。 例如,GenericXmlContextLoader - 默认的ContextLoader - 会基于测试类的名字产生一个默认的位置。 如果类名叫做com.example.MyTest,那么GenericXmlContextLoader就会从"classpath:/com/example/MyTest-context.xml"加载应用上下文。

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "classpath:/com/example/MyTest-context.xml"
@ContextConfiguration
public class MyTest {
    // class body...
}

如果默认位置不适合你的需求,你可以使用一个包含了XML配置元数据的资源位置的数组来配置@ContextConfigurationlocations属性 (假如已经配置好了一个可以使用XML的ContextLoader)- 一般在classpath上,该属性被用来配置应用程序。 这就和在web.xml或者其他部署配置中指定配置列表时,方法完全一样,或者几乎一样。 作为另外一种选择,你可以实现并配置自己的ContextLoader

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "/applicationContext.xml" and "/applicationContext-test.xml"
// in the root of the classpath
@ContextConfiguration(locations={"/applicationContext.xml", "/applicationContext-test.xml"})
public class MyTest {
    // class body...
}

@ContextConfiguration还提供了一个boolean类型的inheritLocations属性以表明是否继承父类的locations。 其默认值是true,表明一个被注解的类会继承被注解的父类中定义的资源位置。 特别地,一个被注解的类的资源位置会附加到其被注解的父类中的资源位置列表上。这样子类就可以继承资源位置列表。 在下面的例子中,将按顺序从"/base-context.xml""/extended-context.xml"中加载针对ExtendedTestApplicationContext。 所以定义在"/extended-context.xml"中的Beans会覆盖掉定义在"/base-context.xml"中的Beans。

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "/base-context.xml" in the root of the classpath
@ContextConfiguration(locations={"/base-context.xml"})
public class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-context.xml" and "/extended-context.xml"
// in the root of the classpath
@ContextConfiguration(locations={"/extended-context.xml"})
public class ExtendedTest extends BaseTest {
    // class body...
}

如果将inheritLocations设为false,那么就会屏蔽掉父类的资源位置,然后可以替换父类中定义的任何资源位置。

默认情况下, 配置好的ApplicationContext一旦被加载就会重用到每个测试上。这样设置的成本仅产生一次(每个测试fixture), 随后测试的执行就会很快了。在某些不太可能发生的情况下,一个测试可能会破坏应用上下文, 这时它需要重新加载 - 例如,通过改变应用对象的bean定义或者状态 - 你可以使用@DirtiesContext (假设默认已经配置了DirtiesContextTestExecutionListener)来注解测试方法使得测试fixture重新加载配置文件并在测试下次执行前重新构建应用上下文。

8.3.7.3. 测试fixture的依赖注入

当你配置DependencyInjectionTestExecutionListener时 - 它会被默认配置 - 通过@TestExecutionListeners注解, 你的测试实例依赖的bean会被注入,而这些bean是通过@ContextConfiguration使用Setter注入、Field注入或者两者都有来注入的, 到底使用哪种方式取决于你选择的注解以及你将它们放到setter方法中还是属性中。为了与Spring 2.5的注解保持一致, 你可以选择Spring的@Autowired注解或者JSR 250中的@Resource注解。其语义对于Spring框架来说都是一致的。 例如, 如果你喜欢 按类型自动织入, 那么请使用@Autowired来注解你的settter方法或者属性。另一方面,如果你喜欢按名字注入, 那么请使用@Resource来注解你的settter方法或者属性。

提示

TestContext框架没有监测测试实例的实例化方式。所以对构造方法使用@Autowired将毫无意义。

既然@Autowired执行按类型自动编织, 那么如果你有相同类型的多个bean定义的话,对那些特定的bean就不能使用该方式。在这种情况下, 你可以使用@Resource按名字注入。另外,如果你的测试类实现了ApplicationContextAware, 就可以直接访问ApplicationContext并调用applicationContext.getBean("titleDao")执行一个显式的查找。

如果你不想让你的测试实例使用依赖注入,只要不将@Autowired或者@Resource注解到任何属性或者setter方法上就行了。 另一种方式,你可以使用@TestExecutionListeners并忽略掉监听器列表中的DependencyInjectionTestExecutionListener.class就可以完全禁用依赖注入。

考虑如下场景:我们有一个类,名字叫HibernateTitleDao通用目标章节已经进行了介绍)。 首先,让我们看看基于JUnit 4.4的测试类的实现,它使用@Autowired进行属性注入(在所有示例代码之后我们会查看应用上下文的配置)。 注意:下面代码的依赖注入行为并不是特定于JUnit 4.4的。同样的依赖注入技术可以使用在任何测试框架中。

@RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration(locations={"daos.xml"})
public final class HibernateTitleDaoTests {

    // this instance will be dependency injected by type
    @Autowired    
    private HibernateTitleDao titleDao;

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }
}

此外,我们可以使用@Autowired进行setter注入。

@RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration(locations={"daos.xml"})
public final class HibernateTitleDaoTests {

    // this instance will be dependency injected by type
    private HibernateTitleDao titleDao;

    @Autowired
    public void setTitleDao(HibernateTitleDao titleDao) {
        this.titleDao = titleDao;
    }

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }
}

现在让我们看看使用@Resource进行属性注入的一个示例。

@RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration(locations={"daos.xml"})
public final class HibernateTitleDaoTests {

    // this instance will be dependency injected by name
    @Resource
    private HibernateTitleDao titleDao;

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }
}

最后,这是使用@Resource进行setter注入的一个示例。

@RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration(locations={"daos.xml"})
public final class HibernateTitleDaoTests {

    // this instance will be dependency injected by name
    private HibernateTitleDao titleDao;
    
    @Resource
    public void setTitleDao(HibernateTitleDao titleDao) {
        this.titleDao = titleDao;
    }

    public void testLoadTitle() throws Exception {
        Title title = this.titleDao.loadTitle(new Long(10));
        assertNotNull(title);
    }
}

上面的代码使用了相同的XML上下文文件,@ContextConfiguration注解使用了这些信息(如 "daos.xml"),它是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- this bean will be injected into the HibernateTitleDaoTests class -->
    <bean id="titleDao" class="com.foo.dao.hibernate.HibernateTitleDao">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <!-- dependencies elided for clarity -->
    </bean>

</beans>

8.3.7.4. 事务管理

在TestContext框架中,事务是由TransactionalTestExecutionListener进行管理的, 默认情况下这是通过@TestExecutionListeners注解进行配置的, 即使你没有在测试类中显式声明 @TestExecutionListeners注解。 为了支持事务,你必须通过@ContextConfiguration在应用上下文中加载一个PlatformTransactionManager bean。 此外,你必须在类或方法层次上声明一个@Transactional

请参考TestContext框架的注解支持中的@TransactionConfiguration以了解类层次的事务配置(例如为事务管理器设置bean名称以及默认的回滚标志)。

为每个测试方法配置事务时有几种选项。如果对于整个类来说事务不可用,那么可以使用@Transactional来显式注解方法。 与此类似,如果对于整个类来说事务可用,那么可以使用@NotTransactional来注解方法表明不为该方法增加事务。 你可以使用@Rollback注解覆盖类级别的默认的回滚设置进而针对一个特定的测试方法控制其事务的提交。

请注意,AbstractTransactionalJUnit38SpringContextTestsAbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests已经在类级别预先配置好了事务支持。

偶尔你需要在一个事务性测试方法前、后执行某些代码,而这些代码是处在事务上下文之外的,例如, 在测试执行前去验证初始的数据库状态或者在测试执行后验证期待的事务提交行为(举例来说,该测试被配置为不进行回滚的)。 支持@BeforeTransaction@AfterTransaction注解的TransactionalTestExecutionListener正好适用于这种情况。 使用这些注解之一来注解测试类中任何的public void方法, 同时TransactionalTestExecutionListener会保证你的事务方法之前的代码或者事务方法之后的代码会在正确的时间执行。

提示

任意前置方法 (如使用JUnit 4的@Before所注解的方法)和后置方法 (如使用JUnit 4的@After所注解的方法)都会一个事务中得到执行。 此外,使用 @NotTransactional注解的测试不会执行@BeforeTransaction@AfterTransaction所注解的方法。

下面的基于JUnit 4的示例展示了一个假想的集成测试场景,重点阐述了事务相关的注解。请查看参考手册的TestContext框架注解支持章节以了解进一步的信息和配置示例。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
@Transactional
public class FictitiousTransactionalTest {

	@BeforeTransaction
	public void verifyInitialDatabaseState() {
		// logic to verify the initial state before a transaction is started
	}

	@Before
	public void setUpTestDataWithinTransaction() {
		// set up test data within the transaction
	}

	@Test
	// overrides the class-level defaultRollback setting
	@Rollback(true)
	public void modifyDatabaseWithinTransaction() {
		// logic which uses the test data and modifies database state
	}

	@After
	public void tearDownWithinTransaction() {
		// execute "tear down" logic within the transaction
	}

	@AfterTransaction
	public void verifyFinalDatabaseState() {
		// logic to verify the final state after transaction has rolled back
	}

	@Test
	@NotTransactional
	public void performNonDatabaseRelatedAction() {
		// logic which does not modify database state
	}
}

8.3.7.5. TestContext支持类

8.3.7.5.1. JUnit 3.8支持类

org.springframework.test.context.junit38包为基于JUnit 3.8的测试用例提供了支持类。

  • AbstractJUnit38SpringContextTests

    对集成了Spring TestContext Framework与JUnit 3.8环境中的ApplicationContext测试支持的TestCase进行了抽象。 当你继承AbstractJUnit38SpringContextTests类时,你就可以访问到protected的成员变量:

    • applicationContext:使用它进行显式的bean查找或者测试整个上下文的状态。

  • AbstractTransactionalJUnit38SpringContextTests

    对为JDBC访问增加便捷功能的AbstractJUnit38SpringContextTests事务扩展进行抽象。 需要在ApplicationContext中定义一个javax.sql.DataSource bean和一个PlatformTransactionManager bean。 当你继承AbstractTransactionalJUnit38SpringContextTests类时,你就可以访问到protected的成员变量:

    • applicationContext:从AbstractJUnit38SpringContextTests父类继承。使用它执行bean的查找或者测试整个上下文的状态

    • simpleJdbcTemplate:在查询以确认状态时非常有用。例如,应用代码要创建一个对象,然后使用ORM工具将其持久化, 这时你想在测试代码执行前后对其进行查询,以确定数据是否插入到数据库中(Spring会保证该查询运行在相同事务内)。 你需要告诉你的ORM工具‘flush’其改变以正确完成任务,例如,使用HibernateSession接口的flush()方法。

8.3.7.5.2. JUnit 4.4支持类

org.springframework.test.context.junit4包为基于JUnit 4.4的测试用例提供了支持类。

  • AbstractJUnit4SpringContextTests

    对集成了Spring TestContext Framework与JUnit 4.4环境中的ApplicationContext测试支持的基本测试类进行了抽取。

    当你继承AbstractJUnit4SpringContextTests时,你就可以访问到protected的成员变量:

    • applicationContext:使用它进行显式的bean查找或者测试整个上下文的状态。

  • AbstractTransactionalJUnit4SpringContextTests

    对为JDBC访问增加便捷功能的AbstractJUnit4SpringContextTests事务扩展进行抽象。 需要在ApplicationContext中定义一个javax.sql.DataSource bean和一个PlatformTransactionManager bean。

    当你继承AbstractTransactionalJUnit4SpringContextTests类时,你就可以访问到下列protected的成员变量:

    • applicationContext:继承自父类AbstractJUnit4SpringContextTests。 使用它执行bean的查找或者测试整个上下文的状态

    • simpleJdbcTemplate:当通过查询来确认状态时非常有用。例如,应用代码要创建一个对象, 然后使用ORM工具将其持久化,这时你想在测试代码执行前后对其进行查询,以确定数据是否插入到数据库中。 (Spring会保证该查询运行在相同事务内。)你需要告诉你的ORM工具‘flush’其改变以正确完成任务,例如, 使用HibernateSession接口的flush()方法。

提示

这些类仅仅为扩展提供了方便。 如果你不想将你的测试类绑定到Spring的类上 - 例如,如果你要直接扩展你想测试的类 - 只需要通过@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration@TestExecutionListeners等注解来配置你自己的测试类就可以了。

8.3.7.5.3. 定制JUnit 4.4运行器

Spring TestContext Framework通过一个可定制的运行器提供了与JUnit 4.4的完全集成。 通过使用@Runwith(SpringJUnit4ClassRunner.class)来注解测试类,开发者可以实现标准的JUnit 4.4单元和集成测试, 同时还能获得TestContext框架的好处,如对加载应用上下文的支持,测试实例的依赖注入,执行事务性测试方法等等。 下面的代码清单显示了使用定制的Spring Runner来配置一个测试类的最小需求。 注意,我们使用一个空的列表来配置@TestExecutionListeners以便禁用默认的监听器, 否则需要通过@ContextConfiguration配置一个 ApplicationContext

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

	@Test
	public void testMethod() {
		// execute test logic...
	}
}
8.3.7.5.4. TestNG支持类

org.springframework.test.context.testng包为基于TestNG的测试用例提供了支持类。

  • AbstractTestNGSpringContextTests

    对集成了Spring TestContext Framework与TestNG环境中的ApplicationContext测试支持的基础测试类进行了抽象。

    当你继承AbstractTestNGSpringContextTests时,就可以访问到下列protected的成员变量:

    • applicationContext:使用它进行显式的bean查找或者测试整个上下文的状态。

  • AbstractTransactionalTestNGSpringContextTests

    对为JDBC访问增加便捷功能的AbstractTestNGSpringContextTests事务扩展进行抽象。 需要在ApplicationContext中定义一个javax.sql.DataSource bean和一个PlatformTransactionManager bean。

    当你继承AbstractTransactionalTestNGSpringContextTests类时,就可以访问下列protected的成员变量:

    • applicationContext:继承自父类AbstractTestNGSpringContextTests。使用它执行bean的查找或者测试整个上下文的状态。

    • simpleJdbcTemplate:当通过查询来确认状态时非常有用。例如,应用代码要创建一个对象, 然后使用ORM工具将其持久化,这时你想在测试代码执行前后对其进行查询,以确定数据是否插入到数据库中。(Spring会保证该查询运行在相同事务内。) 你需要告诉你的ORM工具‘flush’其改变以正确完成任务,例如,使用HibernateSession接口的flush()方法。

提示

这些类仅仅为扩展提供了方便。 如果你不想将你的测试类绑定到Spring的类上 - 例如,如果你要直接扩展你想测试的类 - 只需要通过 @ContextConfiguration@TestExecutionListeners等注解来配置你自己的测试类就可以了。 并使用TestContextManager来手工监测你的测试类。 请查看AbstractTestNGSpringContextTests的源代码以了解如何检测你自己的测试类。

8.3.7.6. TestContext框架注解支持

Spring TestContext Framework支持通用注解章节提到的所有注解。 然而下面的这些注解只有配合JUnit才能使用(比如搭配SpringJUnit4ClassRunner或者 JUnit 3.8JUnit 4.4支持类)。

  • @IfProfileValue

  • @ProfileValueSourceConfiguration

  • @ExpectedException

    协同使用Spring的@ExpectedException注解与JUnit 4的@Test(expected=...)会导致一个不可避免的冲突。 因此当与JUnit 4集成时,开发者必须选择其中一个,在这种情况下建议使用显式的JUnit 4配置。

  • @Timed

    Spring的@Timed注解与JUnit 4的@Test(timeout=...)支持具有不同的语义。 特别地,鉴于JUnit 4处理测试执行超时(如通过在一个单独的线程中执行测试方法)的方式, 我们不可能在一个事务上下文中的测试方法上使用JUnit的@Test(timeout=...)配置。因此, 如果你想将一个测试方法配置成计时具事务性的, 你就必须联合使用Spring的@Timed@Transactional注解。 还值得注意的是@Test(timeout=...)只管测试方法本身执行的次数,如果超出的话立刻就会失败; 然而,@Timed关注的是测试执行的总时间(包括建立和销毁操作以及重复),并且不会令测试失败。

  • @Repeat

Spring TestContext Framework还支持下面这些非特定于测试的注解,并且保持其语义不变。

  • @Autowired

  • @Qualifier

  • @Resource (javax.annotation)如果JSR-250可用

  • @PersistenceContext (javax.persistence)如果JPA可用

  • @PersistenceUnit (javax.persistence)如果JPA可用

  • @Required

  • @Transactional

下面的列表包含了特定于Spring TestContext Framework的所有注解。请查看相应的JavaDoc以了解进一步的信息,包括默认的属性值等等。

  • @ContextConfiguration

    定义类级别的元数据以决定如何加载和配置ApplicationContext。特别地, @ContextConfiguration定义了要加载的应用上下文资源位置以及用来加载上下文的ContextLoader策略。

    @ContextConfiguration(locations={"example/test-context.xml"}, loader=CustomContextLoader.class)
    public class CustomConfiguredApplicationContextTests {
        // class body...
    }

    注意:@ContextConfiguration默认情况下为继承的资源位置提供了支持。 查看上下文管理和缓存章节及JavaDoc来了解更多的示例和细节信息。

  • @TestExecutionListeners

    定义类级别的元数据,TestExecutionListeners会使用TestContextManager进行注册。 通常,@TestExecutionListeners@ContextConfiguration会搭配使用。

    @ContextConfiguration
    @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
    public class CustomTestExecutionListenerTests {
        // class body...
    }

    注意:@TestExecutionListeners默认情况下为继承的监听器提供了支持。查看JavaDoc来了解更多的示例和细节信息。

  • @TransactionConfiguration

    为配置事务性测试定义了类级别的元数据。特别地,如果需要的PlatformTransactionManager不是“transactionManager”的话, 那么可以显式配置驱动事务的PlatformTransactionManager的bean名字。此外, 可以将defaultRollback标志改为false。通常, @TransactionConfiguration@ContextConfiguration搭配使用。

    @ContextConfiguration
    @TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
    public class CustomConfiguredTransactionalTests {
        // class body...
    }
  • @BeforeTransaction

    表明被注解的public void方法应该在测试方法的事务开始之前执行, 该事务是通过@Transactional注解来配置的。

    @BeforeTransaction
    public void beforeTransaction() {
        // logic to be executed before a transaction is started
    }
  • @AfterTransaction

    表明被注解的public void方法应该在测试方法的事务结束之后执行, 该事务是通过@Transactional注解来配置的。

    @AfterTransaction
    public void afterTransaction() {
        // logic to be executed after a transaction has ended
    }

8.3.8. PetClinic示例

在Spring的完整发行包里包含了PetClinic示例应用,它以JUnit 4.4环境阐述了Spring TestContext Framework的几个特性。 大多数功能包含在AbstractClinicTests里,部分内容列举如下:

@ContextConfiguration
public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests {

	@Autowired
	protected Clinic clinic;

	@Test
	public void getVets() {
		Collection<Vet> vets = this.clinic.getVets();
		assertEquals("JDBC query must show the same number of vets",
			super.countRowsInTable("VETS"), vets.size());
		Vet v1 = EntityUtils.getById(vets, Vet.class, 2);
		assertEquals("Leary", v1.getLastName());
		assertEquals(1, v1.getNrOfSpecialties());
		assertEquals("radiology", (v1.getSpecialties().get(0)).getName());
		// ...
	}
	
	// ...
}

注意:

  • 该测试用例继承了AbstractTransactionalJUnit4SpringContextTests类, 从这里它继承了针对依赖注入的配置(通过DependencyInjectionTestExecutionListener)和事务性行为(通过TransactionalTestExecutionListener)。

  • clinic成员变量 - 要测试的应用程序对象 - 是通过@Autowired进行依赖注入的。

  • testGetVets()方法说明如何使用继承下来的countRowsInTable()方法来轻松验证表中的行数, 进而测试应用代码的正确行为。这点允许实现更强大的测试,减少了对确切测试数据的依赖。例如,无需打断测试就可以向数据库中增加新行。

  • 像很多使用数据库的集成测试一样,AbstractClinicTests中的大多数测试依赖于测试运行前数据库中已有的最小量的数据。 但是你可能在测试用例中改变数据库——当然,在同一个事务中。

PetClinic应用支持三种数据访问技术 - JDBC、Hibernate及JPA。无需任何特定的资源位置, 只要声明了@ContextConfiguration,那么AbstractClinicTests类就会从默认位置加载其应用上下文, 该默认位置为"AbstractClinicTests-context.xml",这里声明了一个通用的DataSource。 子类指定了额外的上下文位置,这就要求它必须声明一个PlatformTransactionManagerClinic的一个具体实现。

例如,PetClinic测试的Hibernate实现包含以下实现。针对这个例子请注意,HibernateClinicTests没有包含一行代码: 我们只需声明@ContextConfiguration并且测试继承于AbstractClinicTests。 既然无需任何特定的资源位置就可以声明@ContextConfiguration, 那么Spring TestContext Framework就会从"AbstractClinicTests-context.xml" (例如继承的位置)和 "HibernateClinicTests-context.xml"中加载应用上下文, 同时"HibernateClinicTests-context.xml" 中定义的bean会覆盖掉"AbstractClinicTests-context.xml"中定义的bean。

@ContextConfiguration
public class HibernateClinicTests extends AbstractClinicTests { }

正如你在PetClinic应用中所看到的,Spring配置文件被划分成多个文件。对于大型应用来说都是这样做的, 配置位置通常被指定在一个针对该应用程序集成测试的通用基类中。 这样的基类还可以增加有用的实例变量 - 很自然地由依赖注入组装 - 例如使用Hibernate的应用中的HibernateTemplate

从长远来看,集成测试中的Spring配置文件应该与部署环境中的一样。一个可能的不同点是数据库连接池和事务基础设施。 如果你正部署到一个完整的应用服务器上,那你可能会使用其连接池(通过JNDI访问)和JTA实现。 这样依赖,在生产阶段你会使用JndiObjectFactoryBean来获得DataSourceJtaTransactionManager。 在容器外的集成测试中无法使用JNDI和JTA,因此你应该为他们使用一个替代的组合, 如Commons DBCP BasicDataSourceDataSourceTransactionManager或者HibernateTransactionManager。 你可以将这种不同的行为放到一个单独的XML文件中,在应用服务器和独立于其他配置的'本地'配置中自由选择,这不会在测试和产品环境中造成差异。 此外,建议使用属性文件来存放连接信息:请查看PetClinic应用以了解这些。