AbstractTransactionalSpringContextTests专为解决以上问题而生,也就是说前面我们所提及的第3)个问题在此得到了回答。只要继承该类创建测试用例,在默认情况下,测试方法中所包含的事务性数据操作都会在测试方法返回前被回滚。由于事务回滚操作发生在测试方法返回前的点上,所以你可以象往常一样在测试方法体中对数据操作的正确性进行校验。
代码清单 6 UserServiceIntegrateTest: package com.baobaotao.service; import org.springframework.test.AbstractTransactionalSpringContextTests; import com.baobaotao.domain.User; public class UserServiceIntegrateTest extends AbstractTransactionalSpringContextTests { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } @Override protected String[] getConfigLocations() { return new String[]{"baobaotao-service.xml", "baobaotao-dao.xml"}; } public void testRegisterUser(){ ①测试方法中的数据操作将在方法返回前被回滚,不会对数据库 User user = new User(); 产生永久性数据操作,第二次运行该测试方法时,依旧可以 user.setUserId(2); 成功运行。 user.setUserName("john"); user.setPassword("123456"); userService.registerUser(user); User user1 = userService.findUserByUserName("john"); ②对数据操作进行 assertEquals(user.getUserId(), user1.getUserId()); 正确性检验 } }
如果testRegisterUser()是直接继承于AbstractDependencyInjectionSpringContextTests类的测试方法,则重复运行该测试方法就会发生数据冲突问题。但因为它位于继承于AbstractTransactionalSpringContextTests的测试用例类中,测试方法中对数据库的操作会被正确回滚,所以重复运行不会有任何问题。
如果你确实希望测试方法中对数据库的操作持久生效而不是被回滚,Spring也可以满足你的要求,你仅需要在测试方法中添加setComplete()方法就可以了。
public void testRegisterUser(){ … User user1 = userService.findUserByUserName("john"); assertEquals(user.getUserId(), user1.getUserId()); setComplete(); ①测试方法中的事务性数据操作将被提交 }
AbstractTransactionalSpringContextTests还拥有几个可用于初始化测试数据库,并在测试完成后清除测试数据的方法,分别介绍如下:
onSetUpBeforeTransaction()/onTearDownAfterTransaction():子类可以覆盖这两个方法,以便在事务性测试方法运行的前后执行一些数据库初始化的操作并在事务完成后清除之;
onSetUpInTransaction()/onTearDownInTransaction():这对方法和前面介绍的方法完成相同的功能,只不过它们是在测试方法的相同事务中执行的。
AbstractTransactionalSpringContextTests另外还提供了一组用于测试延迟数据加载的方法:endTransaction()/startNewTransaction()。我在测试Hibernate、JPA等允许延迟数据加载的应用时,如何模拟数据在Service层事务中被部分加载,当传递到Web层时重新打开事务完成延迟部分数据加载的测试场景呢?这两个方法即为此用途而生:你可以在测试方法中显式调用endTransaction()方法以模拟从Service层中获取部分数据后返回,尔后,再通过startNewTransaction()开启一个和原事务无关新事务——模拟在Web层中重新打开事务,接下来你就可以访问延迟加载的数据,看是否一切如期所料了。
在代码清单 6的②处,我们通过UserService#findUserByUserName()方法对前面registerUser(user)方法数据操作的正确性进行检验。应该说,我们非常幸运,因为在UserService中刚好存在一个可用于检测registerUser(user)数据操作正确性的方法。让我们考虑另外的一种情况:要是 UserService不存在这样的方法,我们该如何检测registerUser(user)数据操作结果的正确性呢?显然我们不能使用肉眼观察的方法,那难道为了验证数据操作正确性专门编写一个配合性的数据访问类不成?
通过JDBC访问数据库,检测数据操作正确性
正当我们“山重水复疑无路”的时候,让我们再往前走上一程,柳暗花明将倏忽而至
——AbstractTransactionalDataSourceSpringContextTests就是花开景明之所。该类继承于AbstractTransactionalSpringContextTests,它添加了一个JdbcTemplate,你可以借由此道快意直达数据库。它自动使用Spring容器中的数据源(DataSource)创建好一个JdbcTemplate实例并开放给子类使用。值得注意的是,如果你采用byName自动装配机制,数据源Bean的名称必须取名为“dataSource”。
让我们对UserServiceIntegrateTest进行改造,以便让其自动拥有访问数据库的设施(JdbcTemplate),并用灵活的方法访问数据库进行数据操作的检验,其代码如下所示:
代码清单 7 UserServiceIntegrateWithJdbcTest package com.baobaotao.service; import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; … public class UserServiceIntegrateWithJdbcTest extends AbstractTransactionalDataSourceSpringContextTests {①注意:继承类发生调整 private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } @Override protected String[] getConfigLocations() { return new String[]{"baobaotao-service.xml", "baobaotao-dao.xml"}; } public void testRegisterUser(){ User user = new User(); user.setUserId(2); user.setUserName("john"); user.setPassword("123456"); userService.registerUser(user); String sqlStr = " SELECT user_id FROM t_user WHERE user_name ='john' "; int userId = jdbcTemplate.queryForInt(sqlStr); ①可以直接使用JdbcTemplate访问数据库了 assertEquals(user.getUserId(), userId); setComplete(); } }
jdbcTemplate是AbstractTransactionalDataSourceSpringContextTests类中定义的,子类可以直接使用它访问数据库。这样我们就可以灵活地访问数据库以检验目标测试方法的数据操作正确性。至此,我们终于毕其功于一役于AbstractTransactionalDataSourceSpringContextTests,顺利解决前面我们中指出的最后问题。
只要你通过扩展AbstractTransactionalSpringContextTests及其子类创建测试用例,所有测试方法都会工作了事务环境下。也就是说,即使某些测试方法不需要访问数据库,也会产生额外的事务管理开销,是否可以对测试方法启用事务管理的行为进行控制呢?此外,在一些情况下,除对目标方法逻辑运行的正确性进行检验外,我们还希望对目标方法的运行性能进行测试:如当目标方法运行时间超过200毫秒时,则测试用例视为未通过。诸如此类的问题,我们目前学习到的知识还不能很好的应付。Spring 2.0新增了注解驱动的测试工具为我们指明了道路,你仅需要通过简单为测试方法标注注解,我们刚才提出的“疑难”问题就可以迎刃而解了。
小结
本文我们讲述了使用Spring提供的一套测试工具对Spring应用程序进行集成测试所需的所有知识。
Spring建议你不应该在单元测试时使用到Spring容器,你应该在集成测试时才使用到Spring容器。手工创建测试固件或者手工装配测试固件的工作都是单调乏味没有创意的工作,通过使用Spring为集成测试提供了帮助类,你就可以享受测试固件自动装配的好处,将精力集中到目标类逻辑测试编写的工作上。
应该说大部分的Java应用都是Web应用,而大部分的Java Web应用都是数据库相关的应用,对数据库应用进行测试经常要考虑数据准备、数据库现场恢复、灵活访问数据以验证数据操作正确性等等的问题。这些问题如果没有一个很好的支持工具,将给编写测试用例造成挑战,幸好Spring都为我们搭建好满足这些需求的测试平台,你仅需要在此基础上编写特定的测试用例就可以了。