Spring 单元测试和集成测试

单元测试:
尽量屏蔽各个模块间的相互干扰,重点关注模块内部逻辑的正确性。
集成测试:将模块整合到一起后进行的测试,它的目的是在于发现一些模块间整合的问题。
原则:You shoule not normally use the Spring container for unit tests:simply popolate your POJOs in plains Junit tests!
在单元测试时,你不应该依赖于Spring容器。
而集成测试需要启动Spring容器,你可以在测试类中简单地从Spring容器中取出目标Bean进行测试。
传统的方式编写集成测试用例的不足(加载applicationContext.xml):
1.导致多次Spring容器初始化的问题。
2.需要使用硬编码方式手工获取Bean。
3.数据库现场容易遭受破坏,举个例子,你在测试方法中插入一条ID为1的User记录,第一次运行不会有问题,第二次运行时,就会因为主键冲突而导致测试用例失败。
4.没有对数据操作正确性进行检查。
既然传统方式对Spring应用进行集成测试存在这么多不足,Spring责无旁贷地担当起革新之任,它通过扩展JUnit框架提供了一套专门测试Spring应用的有力工具。借助Spring集成测试工具的帮助,以上问题都会迎刃而解!
++++++++++++++++++++
如下图所示,Spring API提供了如下几个扩展类,丰富了TestCase的功能。

下面我们逐个认识这些类的作用:
Spring团队推荐使用:Spring TestContext框架来进行所有新的单元测试和集成测试,注意:Spring TestContext框架需要Java5+支持。
通用目标:
跨越各个测试案例执行期的Spring IoC容器缓存;
测试fixture实例的依赖注入;
适合集成测试的事务管理;
Spring特有的支持类在编写集成测试时真的很有用;
上下文管理和缓存:
Spring集成测试支持框架提供了ApplicationContext的持久化载入和这些上下文的缓存机制。
集成测试支持类:
Spring集成测试支持框架提供了几个abstract支持类来简化编写集成测试。
JDBC测试支持:
ITeedu.com 站内搜索:      

  Web  ITEEDU 首页 汇编 C/C++ JAVA Python Ruby J2EE PHP 下载中心 手机 IDE 嵌入式 Linux OS MYSQL sqlite 作品 OpenSource 导航:首页>   webtech  >  j2ee  >  spring25cn  >当前页广告位招租50/月
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集成测试支持框架提供了一些通用目标,包括:

跨越各个测试案例执行期的Spring IoC容器缓存。

测试fixture实例的依赖注入 (这很爽)。

适合集成测试的事务管理(这更加爽)。

Spring特有的支持类在编写集成测试时真的很有用。

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

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

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

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

上下文管理和缓存使用:

JUnit 3.8遗留支持

TestContext框架

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

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

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

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

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

测试fixtures依赖注入使用:

JUnit 3.8 遗留支持

TestContext框架

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

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

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

事务管理使用:

JUnit 3.8 遗留支持

TestContext框架

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

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

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

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

支持类:

JUnit 3.8遗留支持

TestContext框架

8.3.4. JDBC测试支持
org.springframework.test.jdbc包含有SimpleJdbcTestUtils类,它 是一个基于Java5的JDBC相关工具方法集,用来简化标准数据库测试场景。注意AbstractTransactionalJUnit38SpringContextTests, AbstractTransactionalJUnit4SpringContextTests, 和AbstractTransactionalTestNGSpringContextTests 提供了简便的方法来内部代理到SimpleJdbcTestUtils。
常用注解:
@IfProfileValue
@ProfileValueSourceConfiguration
@DirtiesContext
@ExpectedException
@Timed
@Repeat
@Rollback
@NotTransactional
所有上面列举的注解都被支持,但必须与AbstractAnnotationAwareTransactionalTests类联合使用,以保证这些注解能起作用。
JUnit 3.8遗留支持:
Spring JUnit 3.8 遗留支持类打包在org.springframework.test包中。
Junit3.8遗留支持类AbstractTransactionalDataSourceSpringContextTests。
Java 5+ 专有支持:
使用注解的事务相关测试:AbstractAnnotationAwareTransactionalTests类扩展了AbstractTransactionalDataSourceSpringContextTests类.
JPA支持类:
org.springframework.test.jpa包提供了基于Java 持久化API(JPA)的测试支持类。

++++++++++++++++++++++++++++++++++++++++++
下面通过一个例子还具体了解测试类的用法:
Spring TestContext Framework:
@ContextConfiguration  //①
public class TestUserService extends
    AbstractTransactionalJUnit4SpringContextTests {
  
@Autowired  //②
   private UserService userService;

   @Test  //③
   public void handleUserLogin(){
       User user = new User();
       user.setUserId(1);
       user.setLastIp("127.0.0.1");
       Date now = new Date();
       user.setLastVisit(now.getTime());
       userService.handleUserLogin(user);
   }
}
在 ① 处,标注了一个类级的 @ContextConfiguration 注解,这里 Spring 将按 TestContext 契约查找 classpath:/com/baobaotao/service/TestUserService-context.xml 的 Spring 配置文件,并使用该配置文件启动 Spring 容器。@ContextConfiguration 注解有以下两个常用的属性:
•locations:可以通过该属性手工指定 Spring 配置文件所在的位置,可以指定一个或多个 Spring 配置文件。如下所示:
@ContextConfiguration(locations={“xx/yy/beans1.xml”,” xx/yy/beans2.xml”})
•inheritLocations:是否要继承父测试用例类中的 Spring 配置文件,默认为 true。如下面的例子:
@ContextConfiguration(locations={"base-context.xml"})
 public class BaseTest {
     // ...
 }
 @ContextConfiguration(locations={"extended-context.xml"})
 public class ExtendedTest extends BaseTest {
     // ...
 }
 ② 处的 @Autowired 注解让 Spring 容器自动注入 UserService 类型的 Bean。而在 ③ 处标注的 @Test 注解则让 handleUserLogin() 方法成为一个 JUnit 4.4 标准的测试方法, @Test 是 JUnit 4.4 所定义的注解。
具备的功能:事务自动回滚,为了不影响原始数据。
准备测试数据并检测运行结果:
@Before 来达到准备测试数据的效果。
@After 方法执行后处理的操作。和测试方法处于同一个事务中。
而在默认情况下,继承于 AbstractTransactionalJUnit4SpringContextTests 测试用例的所有测试方法都将工作于事务环境下,你可以显式地通过 @NotTransactional 注解,让测试方法不工作于事务环境下。
该类默认暴露的变量有:
simpleJdbcTemplate、applicationContext
暴露的方法有:
•protected int countRowsInTable(String tableName) :计算数据表的记录数。
•protected int deleteFromTables(String... names):删除表中的记录,可以指定多张表。
•protected void executeSqlScript(String sqlResourcePath, boolean continueOnError):执行 SQL 脚本文件,在脚本文件中,其格式必须一个 SQL 语句一行。
检验业务逻辑的正确性:
@Test
public void handleUserLogin(){
    User user = userDao.getUserById(userId);
    user.setLastIp("127.0.0.1");
    Date now = new Date();
    user.setLastVisit(now.getTime());
    userService.handleUserLogin(user);

    //------------------以下为业务执行结果检查的代码---------------------
    User newUser = userDao.getUserById(userId);
    Assert.assertEquals(5, newUser.getCredits()); //①检测积分
    //①检测最后登录时间和IP
    Assert.assertEquals(now.getTime(), newUser.getLastVisit());
    Assert.assertEquals("127.0.0.1",newUser.getLastIp());
      
    // ③检测登录记录
    String sql = "select count(1) from t_login_log where user_id=? "+
        “ and login_datetime=? and ip=?";
    int logCount =simpleJdbcTemplate.queryForInt(sql, user.getUserId(),
        user.getLastVisit(),user.getLastIp());
    Assert.assertEquals(1, logCount);
   }
 
注意:由于我们的 DAO 层采用 Spring JDBC 框架,它没有采用服务层缓存技术,所以可以使用 DAO 类返回数据库中的数据。如果采用 Hibernate 等 ORM 框架,由于它们采用了服务层缓存的技术,为了获取数据库中的相应数据,需要在业务方法执行后调用 HibernateTemplate.flush() 方法,将缓存中的对象同步到数据库中,这时才可以通过 SimpleJdbcTemplate 在数据库中访问业务方法的执行情况。

在前面,我们直接通过扩展 AbstractTransactionalJUnit4SpringContextTests 编写测试用例,在了解了编写基于 TestContext 测试框架的测试用例后,现在是了解 TestContext 测试框架本身的时候了。

TestContext 测试框架的核心由 org.springframework.test.context 包中三个类组成,分别是 TestContext 和 TestContextManager 类以及 TestExecutionListener 接口。其类图如下 图 2 所示:


图 2. Spring TestContext 测试框架核心类
图 2. Spring TestContext 测试框架核心类

  • TestContext:它封装了运行测试用例的上下文;
  • TestContextManager:它是进入 Spring TestContext 框架的程序主入口,它管理着一个 TestContext 实例,并在适合的执行点上向所有注册在 TestContextManager 中的 TestExecutionListener 监听器发布事件:比如测试用例实例的准备,测试方法执行前后方法的调用等。
  • TestExecutionListener:该接口负责响应 TestContextManager 发布的事件。

Spring TestContext 允许在测试用例类中通过 @TestExecutionListeners 注解向 TestContextManager 注册多个监听器,如下所示:

@TestExecutionListeners( { 
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class })
public class TestXxxService{
    …
}

Spring 提供了几个 TestExecutionListener 接口实现类,分别说明如下:

  • DependencyInjectionTestExecutionListener:该监听器提供了自动注入的功能,它负责解析测试用例中 @Autowried 注解并完成自动注入;
  • DirtiesContextTestExecutionListener:一般情况下测试方法并不会对 Spring 容器上下文造成破坏(改变 Bean 的配置信息等),如果某个测试方法确实会破坏 Spring 容器上下文,你可以显式地为该测试方法添加 @DirtiesContext 注解,以便 Spring TestContext 在测试该方法后刷新 Spring 容器的上下文,而 DirtiesContextTestExecutionListener 监听器的工作就是解析 @DirtiesContext 注解;
  • TransactionalTestExecutionListener:它负责解析 @Transaction、@NotTransactional 以及 @Rollback 等事务注解的注解。@Transaction 注解让测试方法工作于事务环境中,不过在测试方法返回前事务会被回滚。你可以使用 @Rollback(false) 让测试方法返回前提交事务。而 @NotTransactional 注解则让测试方法不工作于事务环境中。此外,你还可以使用类或方法级别的 @TransactionConfiguration 注解改变事务管理策略,如下所示:
    @TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
    @Transactional
    public class TestUserService {
        …
    }
    	

我们知道在 JUnit 4.4 中可以通过 @RunWith 注解指定测试用例的运行器,Spring TestContext 框架提供了扩展于 org.junit.internal.runners.JUnit4ClassRunner 的 SpringJUnit4ClassRunner 运行器,它负责总装 Spring TestContext 测试框架并将其统一到 JUnit 4.4 框架中。

Spring TestContext 为基于 JUnit 4.4 测试框架提供了两个抽象测试用例类,分别是 AbstractJUnit4SpringContextTests 和 AbstractTransactionalJUnit4SpringContextTests,而后者扩展于前者。让我们来看一下这两个抽象测试用例类的骨架代码:

@RunWith(SpringJUnit4ClassRunner.class) //① 指定测试用例运行器
@TestExecutionListeners(                 //② 注册了两个TestExecutionListener监听器
    { DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class })
public class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
    …
}

① 处将 SpringJUnit4ClassRunner 指定为测试用例运行器,它负责无缝地将 TestContext 测试框架移花接木到 JUnit 4.4 测试框架中,它是 Spring TestContext 可以运行起来的根本所在。② 处通过 @TestExecutionListeners 注解向测试用例类中注册了两个 TestExecutionListener 监听器,这两个监听器分别负责对 @Autowired 和 @DirtiesContext 注解进行处理,为测试用例提供自动注入和重新刷新 Spring 容器上下文的功能。

AbstractTransactionalJUnit4SpringContextTests 扩展于 AbstractJUnit4SpringContextTests,提供了事务管理的支持,其骨架代码如下所示:

//① 注册测试用例事务管理的监听器
@TestExecutionListeners( { TransactionalTestExecutionListener.class })
@Transactional    //② 使测试用例的所有方法都将工作于事务环境下
public class AbstractTransactionalJUnit4SpringContextTests 
extends AbstractJUnit4SpringContextTests {
    …
}

在 ① 处,AbstractTransactionalJUnit4SpringContextTests 向测试用例类中注册了 TransactionalTestExecutionListener 监听器,这样测试用例中的 @Transaction、@NotTransaction 以及 @Rollback 等注解就可以正确地工作起来了。注意,你不需要在 Spring 配置文件通过 <tx:annotation-driven /> 和 <context:annotation-config/> 为测试用例类启用注解事务驱动和注解自动注入,这个工作完全于 TestContext 自身来解决(通过注册 DependencyInjectionTestExecutionListener 和 TransactionalTestExecutionListener 监听器),毕竟测试用例类没有注册到 Spring 容器中,没有成为 Spring 的 Bean。


我们通过对一个典型的涉及数据库访问操作的 UserService 服务类的测试,讲述了使用 Spring 2.5 TestContext 测试框架进行集成测试的各项问题,这包括测试固件的自动注入、事务自动回滚、通过 SimpleJdbcTemplate 直接访问数据库以及测试数据准备等问题。

在通过一个实际例子的学习后,我们对如何使用 TestContext 测试框架有了一个具体的认识,在此基础上我们对 Spring TestContext 测试框架体系结构进行了分析,然后剖析了 Spring 为 TestContext 嫁接到 JUnit 4.4 测试框架上所提供的两个抽象测试用例类。

Spring 的 TestContext 测试框架不但可以整合到 JUnit 4.4 测试框架上,而且还可以整合到 JUnit 3.8 以及 TestNG 等测试框架上。目前已经提供了对 JUnit 3.8 以及 TestNG 的支持,你可以分别在 org.springframework.test.context.junit38 和 org.springframework.test.context.testng 包下找到整合的帮助类。

 

参考:

http://www.ibm.com/developerworks/cn/java/j-lo-spring25-test/

http://www.iteedu.com/webtech/j2ee/spring25cn/ch08s03.php

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值