详细讲解在spring中进行集成测试

3.5分(超过74%的文档 430阅读 20下载 2011-06-10上传 18 131KB 展开

文库VIP新用户专享:首月仅需14元

开通VIP
VIP
详细讲解在spring中进行集成测试
/ 18
来自
百度
文库

详细讲解在

Spring

中进行集成测试

概述

在单元测试时,

我们尽量在屏蔽模块间相互干扰的情况下,

重点关注模块内部逻辑的正

确性。

而集成测试则是在将模块整合在一起后进行的测试,

它的目的在于发现一些模块间整

合的问题。

有些功能很难通过模拟对象进行模拟,

相反它们往往只能在真实模块整合后,

能真正运行起来,如事务管理就是其中比较典型的例子。

按照

Spring

的推荐(原话:

You should not normally use the Spring container for unit 

tests: simply populate your POJOs in plain JUnit tests!

,在单元测试时,你不应该依赖于

Spring

容器。换言之,你不应该在单元测试时启动

ApplicatonContext

并从中获取

Bean

相反你应该通过模拟对象完成单元测试。

而集成测试的前提则是事先装配好模块和模块之间

的关联类,如将

DAO

层真实的

UserDao

LoginLogDao

装配到

UserServiceImpl

再进行

测试。具体装配工作是在

Spring

配置文件中完成的,因此在一般情况下,集成测试需要启

Spring

容器,你可以在测试类中简单地从

Spring

容器中取出目标

Bean

进行测试。

需要测试的业务接口

假设我们的应用中拥有一个

UserService

业务层接口,它拥有

4

个业务方法,其代码如下所

示:

代码清单

1 UserServie

接口

package com.baobaotao.service; 

import com.baobaotao.domain.User; 

import org.springframework.transaction.annotation.Transactional; 

@Transactional 

public interface UserService { 

boolean hasMatchUser(String userName,String password); 

User findUserByUserName(String userName); 

void loginSuccess(User user); 

void registerUser(User user); 

} 

我们通过

UserServiceImpl

UserService

提供了实现:

代码清单

2 UserServiceImpl

实现

UserService

接口

package com.baobaotao.service; 

import com.baobaotao.dao.LoginLogDao; 

import com.baobaotao.dao.UserDao; 

import com.baobaotao.domain.LoginLog; 

import com.baobaotao.domain.User; 

public class UserServiceImpl implements UserService { 

private UserDao userDao; 

private LoginLogDao loginLogDao; 

public boolean hasMatchUser(String userName, String password) { 

int matchCount =userDao.getMatchCount(userName, password); 

return matchCount > 0; 

} 

public User findUserByUserName(String userName) { 

return userDao.findUserByUserName(userName); 

} 

public void loginSuccess(User user) { 

user.setCredits( 5 + user.getCredits()); 

LoginLog loginLog = new LoginLog(); 

loginLog.setUserId(user.getUserId()); 

loginLog.setIp(user.getLastIp()); 

loginLog.setLoginDate(user.getLastVisit()); 

userDao.updateLoginInfo(user); 

loginLogDao.insertLoginLog(loginLog); 

} 

public void setLoginLogDao(LoginLogDao loginLogDao) { 

this.loginLogDao = loginLogDao; 

} 

public void setUserDao(UserDao userDao) { 

this.userDao = userDao; 

} 

} 

UserServiceImpl

引用了两个

DAO

层的类(

UserDao

LoginLogDao

)共同实现

UserService

的接口,在

UserServiceImpl

开放使用之前,我们有必须对其进行集成测试,

以保证实现逻辑的正确性。

使用传统的方式进行集成测试

下面,

我们通过传统的方式为

UserServiceImpl

编写了一个集成测试用例,

测试代码如下所

示:

代码清单

3 TestUserService

UserService

集成测试用例

package com.baobaotao.service; 

public class TestUserService extends TestCase { 

public ApplicationContext ctx = null; 

Spring

容器引用

private static String[] CONFIG_FILES = { 

Spring

配置文件

"baobaotao-dao.xml", 

"baobaotao-service.xml" 

}; 

protected void setUp() throws Exception {

启动

Spring

容器

ctx = new FileSystemXmlApplicationContext(CONFIG_FILES); 

} 

public void testHasMatchUser() { 

测试方法一

-1

从容器中获取

Bean 

UserService userService = (UserService) ctx.getBean("userService"); 

boolean b1 = userService.hasMatchUser("admin", "123456"); 

boolean b2 = userService.hasMatchUser("admin", "1111"); 

assertTrue(b1); 

assertTrue(!b2); 

} 

public void testAddLoginLog() {

测试方法二

-1

从容器中获取

Bean 

UserService userService = (UserService) ctx.getBean("userService"); 

User user = userService.findUserByUserName("admin"); 

user.setUserId(1); 

user.setUserName("admin"); 

user.setLastIp("192.168.12.7"); 

user.setLastVisit(new Date()); 

userService.loginSuccess(user); 

} 

…//

省略其余的测试方法

} 

在这个测试用例中,我们使用了最原始的

JUnit

T

estCase

进行集成测试,乍一看并没有

多大的问题,但仔细分析一下,我们就可以总结出以下四点明显的不足:

1)

导致多次

Spring

容器初始化问题:

根据

JUnit

测试方法的调用流程

(参见错误!

未找到引

用源。

小节的描述)

每执行一个测试方法都会创建一个

TestUserService

实例并调用

setUp()

方法。由于我们在

setUp()

方法中初始化

Spring

容器,这意味着

TestUserService

有多少个

测试方法,

Spring

容器就会被重复初始化多少次。虽然初始化

Spring

容器的速度并不会太

慢,但由于可能会在

Sprnig

容器初始化时执行加载

Hibernate

映射文件等耗时的操作,如

果每执行一个测试方法都必须重复初始化

Spring

容器,

则对测试性能的影响是不容忽视的;

2)

需要使用硬编码方式手工获取

Bean

:在

-1

-1

处,我们通过

ctx.getBean()

方法从

Spring

容器中获取需要测试的目标

Bean

,并且还要进行强制类型转换的造型操作。这种乏

味的操作迷漫在测试用例的代码中,让人觉得繁琐不堪;

3)

数据库现场容易遭受破坏:

处的测试方法会对数据库记录进行插入操作,虽然是针对开

发数据库进行操作,

但如果数据操作的影响是持久的,

可能会影响到后面的测试行为。

举个

例子,你在测试方法中插入一条

ID

1

User

记录,第一次运行不会有问题,第二次运行

时,

就会因为主键冲突而导致测试用例失败。

所以应该既能够完成功能逻辑检查,

又能够在

测试完成后恢复现场,不会留下

后遗症

4)

没有对数据操作正确性进行检查:

处我们向登录日志表插入了一条成功登录日志,可是

我们却没有对

t_login_log

表中是否确实添加了一条记录进行检查。原来我们的方式是打开

数据库,肉眼观察是否插入了相应的记录,但这严重违背了自动测试的原则。试想,你在测

试包括成千上万个数据操作行为的程序时,如何用肉眼进行检查?

既然使用传统方式对

Spring

应用进行集成测试存在这么多不足,

Spring

责无旁贷地担当起

革新之任。

它通过扩展

JUnit

框架提供了一套专门测试

Spring

应用的有力工具。

借助

Spring

集成测试工具的帮助,以上所罗列的种种问题将冰消雪融、云开雾散。

Spring

提供的测试帮助类

Spring

org.springframework.test

包中为测试提供了几个有用的类,它们都是

JUnit 

TestCase

的子类。通过层层扩展,不断丰富测试的功能,我们可以通过下图了解这些类的

继承关系:

1 Spring

测试工具类

下面,

我们来逐个了解这棵承继类树中每个节点测试类的功用,

第一个要认识的是直接扩展

TestCase

ConditionalTestCase

测试类。

ConditionalT

estCase

如果你直接通过扩展

TestCase

创建测试用例,

则所有带

test

前缀的测试方法都会被毫无例

外地执行。

ConditionalT

estCase

可以让你在某些情况下,

有选择地关闭掉一些测试方法,

不让他们在测试用例中执行。

这给开发者带来了很大的灵活性,

因为他们可以在某次测试中

关闭掉一些测试方法,而仅运行当前特别关注的测试方法,将问题域聚集到一定范围内。

如果你要关闭某个测试方法行,仅需实现

ConditionalTestCase

isDisabledInThisEnvironment(String testMethodName)

方法就可以了,

ConditionalT

estCase

在运行每一个测试方法前会根据

isDisabledInThisEnvironment()

方法

判断是简单放弃目标方法的运行,

还是按正常方式执行之。

该方法默认情况下对所有的测试

方法都返回

false

,也即执行所有的测试方法。让我们来看一个具体例子:

代码清单

4 

ConditionalTest1

有条件执行测试方法

package com.baobaotao.test; 

import org.springframework.test.ConditionalTestCase; 

public class ConditionalTest1 extends ConditionalTestCase { 

被忽略不执行的测试方法

private static String[] IGNORED_METHODS = {"testMethod1","testMethod3"}; 

@Override 

protected boolean isDisabledInThisEnvironment(String testMethodName) {

所有在

for (String method : IGNORED_METHODS) { IGNORED_METHODS

数组中

if (method.equals(testMethodName)) { 

的方法都忽略执行。

return true; 

} 

} 

return false; 

} 

public void testMethod1(){ 

不执行

System.out.println("method1"); 

} 

public void testMethod2(){ 

执行

System.out.println("method2"); 

} 

public void testMethod3(){ 

不执行

System.out.println("method3"); 

} 

} 

如果我们直接承继

JUnit

TestCase

处的三个测试方法都会被执行,但现在我

们通过继承

ConditionalTestCase

编写测试类,并覆盖了

isDisabledInThisEnvironment()

法,当测试方法名位于

IGNORED_METHODS

数组中时,测试方法就被旁路掉了。因此当

运行

ConditionalTest1

时,

你会发现只有

处的

testMethod2()

测试方法得到了执行,

其它两

个测试方法看起来也被成功执行,

只不过会程序日志会给出报告,

告诉你哪些测试方法是真

正被执行,而哪些方法被

伪执行

的。

ConditionalT

estCase

其实可用于任何程序的单元测试中,

它本身并没有和

Spring

容器有任

何关联,它仅添加了一个按条件执行测试方法的功能。

AbstractSpringContextTests

AbstractSpringContextTests

扩展于

ConditionalT

estCase

它维护了一个

static

类型的缓存

器(

HashMap

,它使用键保存

Spring ApplicationContext

实例,这意味着

Spring 

ApplicationContext

JVM

级的,不同测试用例、不同测试方法都可以共享这个实例。也

就是说,在运行多个测试用例和测试方法时,

Spring

容器仅需要实例化一次就可以了,极

大地提高了基于

Spring

容器测试程序的运行效率。

Spring

通过这个测试帮助类解决了前面

我们所指出的第

1)

个问题。

AbstractSingleSpringContextTests

AbstractSingleSpringContextTests

继承于

AbstractSpringContextTests

它通过一些方法让

你方便地指定

Spring

配置文件所在位置:

String[] getConfigLocations()

:该方法允许你在指定

Spring

配置文件时使用资源类型前缀,

这些资源类型前缀包括:

classpath:

file:

。以类似于

“com/baobaotao/beans.xml”

形式指定

的资源被当成类路径资源处理;

String[] getConfigPaths()

:以

“/”

开头的地址被当成类路径处理,如

“/com/baobaotao/beans.xml”

,而未以

“/”

开头的地址被当成相对于测试类所在包的文件路

径,如

“beans.xml”

表示配置文件在测试类所在类包的目录下;

String getConfigPath()

:和

getConfigPaths()

类似,在仅需指定一个配置文件中使用。

以上三个方法,

它们的优先级和我们介绍的先后顺序对应,

也就是说,

当你在子类中覆盖了

getConfigLocations()

方法后,其它两个方法就没有意义了。所以你仅需选择三者当中适合

的方法进行覆盖,而没有必要同时覆盖多个方法。

AbstractSingleSpringContextTests

将根据这些方法指定的

Spring

配置文件初始化

Spring

容器,

然后将

Spring

容器引用添加到

static

缓存中。

并通过

getApplicationContext()

向子类

开放

ApplicationContext

的引用。

一般情况下,所有的测试类和测试方法都可以共享这个

Spring

容器直到测试完结,不过在

某些极端情况下,测试方法可能会对

Spring

容器进行改动(比如通过程序改变

Bean

的配

置定义)

,如果这种改变对于其它测试方法来说是有干扰的,这就相当于

弄脏

了作为测试

现场的

Spring

容器,

因此在下一个测试方法执行前必须

抹除

这个改变。

你可以简单地在会

弄脏

”Spring

容器的测试方法中添加

setDirty()

方法向

AbstractSingleSpringContextTests

告这一行为,这样在下一个测试方法执行前,

AbstractSingleSpringContextTests

就会重新

加载

Spring

容器以修补被

弄脏

的部分。

虽然你可以直接继承

AbstractSpringContextT

ests

AbstractSingleSpringContextTests

建自己的集成测试用例,不过你大可不必如此着急。

Spring

已经提供了几个功能齐全、实

践性更强的子类,让我们继续探索

Spring

集成测试工具类的精彩篇章吧。

一般集成测试

应该说,

Spring

通过

AbstractSpringContextT

ests

AbstractSingleSpringContextTests

备好了集成测试的一些基础设施,在建筑学上,这叫夯实地基,而

AbstractDependencyInjectionSpringContextTests

是在此地基之上建起的第一幢楼房。

AbstractDependencyInjectionSpringContextTests

所新添的主要功能是其子类的属性能被

Spring

容器中的

Bean

自动装配,你无需手工通过

ApplicationContext.getBean()

从容器中

获取目标

Bean

自行装配。它很好回答了前面我们所指出第

2)

问题,下面我们通过实例进行

学习:

代码清单

5 

DependencyInjectionCtxT

est

package com.baobaotao.test; 

import org.springframework.test.AbstractDependencyInjectionSpringContextT

ests; 

import com.baobaotao.service.UserService; 

public class DependencyInjectionCtxT

est 

extends AbstractDependencyInjectionSpringContextTests { 

private UserService userService; 

public void 

setUserService(UserService userService) {

该属性设置方法会被自动调动

this.userService = userService; 

} 

@Override 

protected String[] getConfigLocations() { 

指定

Spring

配置文件所在位置

return new String[]{"baobaotao-service.xml","baobaotao-dao.xml"}; 

} 

public void testHasMatchUser(){ 

测试方法

boolean match = userService.hasMatchUser("tom","123456"); 

assertEquals(true,match); 

} 

} 

处,我们指定了

Spring

配置文件所在的位置,

AbstractDependencyInjectionSpringContextTests

将使用这些配置文件初始化好

Spring

器,并将它们保存于

static

的缓存中。然后马上着手根据类型匹配机制(

byType

,自动将

Spring

容器中匹配测试类属性的

Bean

通过

Setter

注入到测试类中。为了方便说明这一重

要的特性,我们先看一下

baobaotao-service.xml

的内容:

<beans> 

<tx:annotation-driven/> 

按类型匹配于

DependencyInjectionCtxTest

userService

属性

<bean id="userService" class="com.baobaotao.service.UserServiceImpl"> 

<property name="userDao" ref="userDao"/> 

<property name="loginLogDao" ref="loginLogDao"/> 

</bean> 

</beans> 

根据

baobaotao-service.xml

配置文件的内容,我们知道

Spring

容器中有一个

UserService 

Bean

AbstractDependencyInjectionSpringContextTests

探测到

Spring

容器中存在一个匹

配于

userService

属性的

Bean

后,就将其注入到

DependencyInjectionCtxTest

userService

属性中。

userService

是这个集成测试类的测试固件,因此我们说

AbstractDependencyInjectionSpringContextTests

可以自己装配测试固件。

解决自动装配问题

如果

Spring

容器中拥有多个匹配

UserService

类型的

Bean

,由于

Spring

没有足够的信息

做出取舍决策,因此会抛出

UnsatisfiedDependencyException

异常。假设我们采用以下传

统的事务管理的配置方式对

UserService

进行配置,

按类型匹配的自动装配机制就会引发问

题:

用于被代理的目标

Bean

,按类型匹配于

UserService 

<bean id="userServiceTarget" class="com.baobaotao.service.UserServiceImpl"> 

<property name="userDao" ref="userDao" /> 

<property name="loginLogDao" ref="loginLogDao"></property> 

</bean> 

通过事务代理工厂为

UserServiceImpl

创建的代理

Bean

,也按匹配于

UserService 

<bean id="userService" 

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 

<property name="transactionManager" ref="transactionManager" /> 

<property name="target" ref="userServiceTarget" /> 

<property name="transactionAttributes"> 

</property> 

</bean> 

由于

处和

处的

Bean

都按类型匹配于

UserService

,在对

DependencyInjectionCtxTest

userService

属性进行自动装配将会引发问题。有两种针对该问题的解决办法:

调整配置文件,使按类型匹配于

UserService

Bean

仅有一个,具体有以下两个方法:

处的

Bean

作为

处的内部

Bean

进行装配;

使用基于注解驱动的事务管理配置机制,

这样就无需在配置文件中定义两个

UserService

Bean

了。关于注解驱动事务管理配置的详细信息,请参见

9.6

小节的内容。

改变

DependencyInjectionCtxTest

的自动装配机制:

Spring

默认使用

byType

类型的自动装

配机制,但它允许你通过

setAutowireMode()

的方法改变默认自动装配的机制,比如你可以

调用

setAutowireMode(AUTOWIRE_BY_NAME)

方法启用按名称匹配的自动装配机制。

AbstractDependencyInjectionSpringContextTests

定义了三个代表自动装配机制类型的常

量,分别说明如下:

AUTOWIRE_BY_TYPE

按类型匹配的方式进行自动装配,这个默认的机制;

AUTOWIRE_BY_NAME

按名字匹配的方式进行自动装配

AUTOWIRE_NO

不使用自动装配机制,这意味着你需要手工调用

getBean()

进行装配。

现在我们解决了在自动装配时,因

Spring

容器中存在多个匹配

Bean

而导致的问题,接下

来让我们考察另一个自动装配的问题。

依赖检查

假设我们在

DependencyInjectionCtxT

est

添加一个

User

类型的属性并提供

Setter

方法,

Spring

容器中没有匹配该属性的

Bean

package com.baobaotao.test; 

import com.baobaotao.domain.User; 

public class DependencyInjectionCtxT

est extends 

AbstractDependencyInjectionSpringContextTests { 

private User user; 

public void setUser(User user) { 

this.user = user; 

} 

} 

猜想一下重新运行

DependencyInjectionCtxT

est

将会发生什么情况呢?答案可能让你失望:

UnsatisfiedDependencyException

再次象黑幕一样降临。在默认情况下,

AbstractDependencyInjectionSpringContextTests

要求所有属性都能在

Spring

容器中找到

对应

Bean

,否则抛出异常。

仔细思考一下,这种运行机制并非没有道理,因为既然你已经提供了

Setter

方法,就相当

于给出了这样的暗示信息:

这个属性测试类自身创建不了,必须由外部提供

。而在使用自

动装配机制的情况下,测试类属性自动从

Spring

容器中注入匹配的属性,一般情况下不会

手工去调用

Setter

方法准备属性。

如果你出于一些特殊的理由,

希望在采用自动装配的情况下,

如果有属性未得到装配也不在

乎,那么你可以在测试类构造函数中调用

setDependencyCheck(false)

方法达到目的:

package com.baobaotao.test; 

public class DependencyInjectionCtxT

est extends 

AbstractDependencyInjectionSpringContextTests { 

public DependencyInjectionCtxT

est(){ 

setDependencyCheck(false); 

告知不进行属性依赖性检查

} 

} 

这个

AbstractDependencyInjectionSpringContextTests

就不会对测试类有些属性找不到匹

Bean

而抛出异常了。

在不提供

Setter

方法的情况下自动注入

大多数

IDE

都提供了为属性变量自动生成

Setter

方法的操作,因此客观地说,为属性

编写一个

Setter

方法的工作根本不值一提。

如果你觉得众多的

Setter

方法影响了视觉感观,

但又希望享受测试类属性自动装配的好处,

Spring

也不会让你失望的。你需要做的是以下

两步的工作:

1) 

将需要自动装配的属性变量声明为

protected

2) 

在测试类构造函数中调用

setPopulateProtectedVariables(true)

方法。

package com.baobaotao.test; 

public class DependencyInjectionCtxT

est extends 

AbstractDependencyInjectionSpringContextTests { 

protected UserService userService; 

将属性声明为

protected 

// public void setUserService(UserService userService) { 

大胆将

Setter

方法移除掉

// this.userService = userService; 

// } 

public DependencyInjectionCtxT

est(){ 

setDependencyCheck(false); 

setPopulateProtectedVariables(true); 

启用直接对属性变量进行注释的机制

} 

} 

将属性声明为

protected

后并通过

setPopulateProtectedVariables(true)

启用对属性变量直

接注入的机制(启用反射机制注入)

,你就可以避免为属性变量编写对应的

Setter

方法了

提示

属性如果声明为

public

,虽然你也调用了

setPopulateProtectedVariables(true)

法,属性变量依然不会被自动注入。所以这种机制仅限于

protected

的属性变量。

方便地恢复测试数据库现场

我们现在已经可以通过

AbstractDependencyInjectionSpringContextT

ests

的属性自动

装配机制方便地建立起测试固件,省却手工调用

getBean()

自行准备测试固件的烦恼。当我

们对

UserService

hasMatchUser()

findUserByUserName()

方法进行测试时,

不会有任

何问题,

因为这两个方法仅对数据库执行读操作。

UserService

以下两个接口方法会对数

据库执行更改操作:

void loginSuccess(User user); 

void registerUser(User user); 

当我们对这两个接口方法进行测试时,它们将会在数据库中产生持久化数据。考虑对

registerUser(User user)

方法进行测试时,我们可能编写如下所示的测试方法:

public void testRegisterUser(){ 

User user = new User(); 

user.setUserId(2); 

user.setUserName("john"); 

user.setPassword("123456"); 

userService.registerUser(user); 

} 

当第一次成功运行

testRegisterUser()

测试方法时,

将在数据库中产生一条主键为

2

的记

录,如何第二次重新运行

testRegisterUser()

测试方法其结果将不言自明:因主键冲突导致

测试方法执行失败,

最终报告测试用例没有通过。

在这种情况下,

测试用例未通过并不是因

UserServiceImpl#registerUser(User user)

存在逻辑错误,而是因为测试方法的积累效应

导致外在设施的现场发生变化而引起的问题。

为了防止这种问题,

测试用例必须在保证不对数据库状态产生持久化变化的情况下,

目标类的数据操作逻辑正确性进行检测。乍一听这一要求有点貌似于

既想马儿跑,又想马

儿不吃草

一样充满悖论,实则不然。只要我们让测试方法不提交事务,在测试完后自动回

滚事务,就皆大欢喜了。

让测试方法自动拥有回滚能力

AbstractTransactionalSpringContextT

ests

专为解决以上问题而生,也就是说前面我们

所提及的第

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

的测试方法,则重复运行该测试方法就会发生数据冲突问题。但因为它位于继承于

AbstractTransactionalSpringContextT

ests

的测试用例类中,测试方法中对数据库的操作会

被正确回滚,所以重复运行不会有任何问题。

如果你确实希望测试方法中对数据库的操作持久生效而不是被回滚,

Spring

也可以满

足你的要求,你仅需要在测试方法中添加

setComplete()

方法就可以了。

public void testRegisterUser(){ 

User user1 = userService.findUserByUserName("john"); 

assertEquals(user.getUserId(), user1.getUserId()); 

setComplete(); 

测试方法中的事务性数据操作将被提交

} 

AbstractTransactionalSpringContextT

ests

还拥有几个可用于初始化测试数据库,并在

测试完成后清除测试数据的方法,分别介绍如下:

onSetUpBeforeTransaction()/onTearDownAfterTransaction()

:子类可以覆盖这两个方

法,以便在事务性测试方法运行的前后执行一些数据库初始化的操作并在事务完成后清除

之;

onSetUpInTransaction()/onTearDownInTransaction()

:这对方法和前面介绍的方法完成相

同的功能,只不过它们是在测试方法的相同事务中执行的。

AbstractTransactionalSpringContextT

ests

另外还提供了一组用于测试延迟数据加载的

方法:

endTransaction()/startNewTransaction()

。我在测试

Hibernate

JPA

等允许延迟数

据加载的应用时,如何模拟数据在

Service

层事务中被部分加载,当传递到

Web

层时重新

打开事务完成延迟部分数据加载的测试场景呢?这两个方法即为此用途而生:

你可以在测试

方法中显式调用

endTransaction()

方法以模拟从

Service

层中获取部分数据后返回,尔后,

再通过

startNewTransaction()

开启一个和原事务无关新事务

——

模拟在

Web

层中重新打开

事务,接下来你就可以访问延迟加载的数据,看是否一切如期所料了。

在代码清单

6

处,我们通过

UserService#findUserByUserName()

方法对前面

registerUser(user)

方法数据操作的正确性进行检验。应该说,我们非常幸运,因为在

UserService

中刚好存在一个可用于检测

registerUser(user)

数据操作正确性的方法。

让我们

考虑另外的一种情况:要是

UserService

不存在这样的方法,我们该如何检测

registerUser(user)

数据操作结果的正确性呢?显然我们不能使用肉眼观察的方法,那难道

为了验证数据操作正确性专门编写一个配合性的数据访问类不成?

通过

JDBC

访问数据库,检测数据操作正确性

当我们

山重水复疑无路

的时候,让我们再往前走上一程,柳暗花明将倏忽而至

——

AbstractTransactionalDataSourceSpringContextTests

就是花开景明之所。

该类继承于

AbstractTransactionalSpringContextT

ests

,它添加了一个

JdbcTemplate

,你可以借由此道

快意直达数据库。它自动使用

Spring

容器中的数据源(

DataSource

)创建好一个

JdbcT

emplate

实例并开放给子类使用。

值得注意的是,

如果你采用

byName

自动装配机制,

数据源

Bean

的名称必须取名为

“dataSource”

让我们对

UserServiceIntegrateTest

进行改造,以便让其自动拥有访问数据库的设施

JdbcTemplate

,并用灵活的方法访问数据库进行数据操作的检验,其代码如下所示:

代码清单

7 UserServiceIntegrateWithJdbcTest 

package com.baobaotao.service; 

import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; 

public class UserServiceIntegrateWithJdbcT

est 

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 = jdbcT

emplate.queryForInt(sqlStr); 

可以直接使用

JdbcT

emplate

访问数据库了

assertEquals(user.getUserId(), userId); 

setComplete(); 

} 

} 

jdbcTemplate

AbstractTransactionalDataSourceSpringContextTestsAbstractTransactionalDataSourceS

pringContextTests

顺利解决前面我们中指出的最后问题。

类中定义的,

子类可以直接使用

它访问数据库。这样我们就可以灵活地访问数据库以检验目标测试方法的数据操作正确性。

至此,我们终于毕其功于一役于

只要你通过扩展

AbstractTransactionalSpringContextTests

及其子类创建测试用例,

有测试方法都会工作了事务环境下。

也就是说,

即使某些测试方法不需要访问数据库,

也会

产生额外的事务管理开销,

是否可以对测试方法启用事务管理的行为进行控制呢?此外,

一些情况下,

除对目标方法逻辑运行的正确性进行检验外,

我们还希望对目标方法的运行性

能进行测试:如当目标方法运行时间超过

200

毫秒时,则测试用例视为未通过。诸如此类的

我们目前学习到的知识还不能很好的应付。

Spring 2.0

新增了注解驱动的测试工具为我

们指明了道路,你仅需要通过简单为测试方法标注注解,我们刚才提出的

疑难

问题就可以

迎刃而解了。

小结

本文我们讲述了使用

Spring

提供的一套测试工具对

Spring

应用程序进行集成测试所需

的所有知识。

Spring

建议你不应该在单元测试时使用到

Spring

容器,你应该在集成测试时才使用到

Spring

容器。手工创建测试固件或者手工装配测试固件的工作都是单调乏味没有创意的工

作,通过使用

Spring

为集成测试提供了帮助类,你就可以享受测试固件自动装配的好处,

将精力集中到目标类逻辑测试编写的工作上。

应该说大部分的

Java

应用都是

Web

应用,而大部分的

Java Web

应用都是数据库相

关的应用,

对数据库应用进行测试经常要考虑数据准备、

数据库现场恢复、

灵活访问数据以

验证数据操作正确性等等的问题。

这些问题如果没有一个很好的支持工具,

将给编写测试用

例造成挑战,幸好

Spring

都为我们搭建好满足这些需求的测试平台,你仅需要在此基础上

编写特定的测试用例就可以了