阅读提要 StrutsTestCase是一个强有力的易于使用的针对Struts行为的测试框架
StrutsTestCase
并与传统型JUnit测试相结合
将会带给你一个相当高的测试覆盖率并提高你的产品的可靠性
一引言
StrutsTestCase是一个用于测试Struts行为的基于Junit的测试框架如果你使用Struts那么你会注意到它可以提供给你一种容易而有效的方式来测试你的应用程序的Struts行为类
典型的JEE应用程序都是分层构建的如图所示
·DAO层封装了数据库存取Hibernate映射和对象类Hibernate查询实体EJBs或一些其它的实体关系持续性技术都可以在这一层找到
·商业层包含更高级的商业服务理想地这个商业层将是相对独立于数据库实现在这个层上经常使用会话EJBs
·描述层包含为用户显示应用程序数据并解释用户请求在一个Struts应用程序中这一层典型地使用JSP/JSTL页面来显示数据并且使用Struts行为来解释用户查询
·客户层基本上是运行于用户机器上的web浏览器客户端逻辑(例如JavaScript)有时被放在这里尽管很难对其进行有效地测试
图典型的JEE架构
DAO和商业层的测试或者可以通过使用经典的JUnit测试或者使用各种JUnit扩展来进行具体依赖于架构的实现细节DbUnit是一种用来进行数据库单元测试的良好选择
另一方面测试Struts行为总是很困难的事情即使在商业层严格地限制于商业层的构建时Struts行为也总要包含重要数据校验转换和流程控制代码不对Struts行为进行测试将会在代码覆盖率上留下一道很髒的鸿沟StrutsTestCase会让你填充这条鸿沟
对行为层进行单元测试还带来其它一些益处
·可以更好地规划视图和控制层从而使之更为简单清晰
·更容易重构行为类
·避免冗余的未使用的行为类
·测试实例有助于对行为层进行归档这在创建屏幕时是很有用的
上面是基于测试开发的典型好处并且它们可以应用于在各种情况下使用的Struts行为层
二StrutsTestCase简介
StrutsTestCase工程提供了一种灵活又方便的方法来从JUnit框架内测试Struts行为它能够使你对你的Struts行为进行白色盒子测试通过在调用行为后建立请求参数并检查结果Request或Session的状态
StrutsTestCase允许或者是一个模仿测试方式这时框架模拟web服务器容器或者是一个容器内方式这时使用Cactus框架来从服务器容器(例如Tomcat)内部运行测试一般地我比较喜欢模仿测试方式因为它更为轻量级的且运行更快些并因此允许较宽松的开发周期
所有的StrutsTestCase单元测试类或者派生于MockStrutsTestCase以进行模仿测试或者派生于CactusStrutsTestCase以进行容器内测试在此我们先讨论模仿测试因为它要求较少的配置并且运行较快些
三实战StrutsTestCase
为了使用StrutsTestCase来测试这个行为我们创建一个扩展类MockStrutsTestCase的新类这个类提供一系列方法来构建一个模拟的HTTP请求调用相应的Struts行为以及一旦在行为完成时校验应用程序状态
可以设想有一个在线的具有多条件查找功能的住所数据库这个查找函数是通过/searchdo行为实现的这个行为将基于指定的条件完成一次多条件查找并把结果列表放置在一个称为results的请求范围属性中例如下列URL应该显示一个在法国的所有的住所结果列表
/searchdo?country=FR
现在假定我们想要使用一个测试驱动的方式来实现这个方法我们创建该行为类并更新Struts配置文件我们还编制测试实例来测试(空的)这个行为类通过使用一种严格的测试驱动的开发方法我们可以首先创建测试实例然后实现代码来匹配该测试实例在实践中具体的顺序可能因要测试的代码而有所不同
起始的测试情形看去如下样子
public void testSearchByCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country FR);
actionPerform();
}
在此我们建立要调用的路径(setRequestPathInfo())并且添加一请求参数(addRequestParameter())然后我们用actionPerform()来调用行为类这将验证Struts配置并且调用相应的行动类但是将不测试该行为的实际所做为此我们需要验证行动的结果
public void testSearchByCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country FR);
actionPerform();
verifyNoActionErrors();
verifyForward(success);
assertNotNull(requestgetAttribute(results));
}
在此我们检查三件事情
·不存在ActionError消息(verifyNoActionErrors())
·返回successforward
·results属性被放置到请求范围中
如果我们正在使用tiles我们也可以通过使用verifyTilesForward()来保证successforward实际上指定正确的tiles定义
public void testSearchByCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country FR);
actionPerform();
verifyNoActionErrors();
verifyTilesForward(success accommodationlistdef);
assertNotNull(requestgetAttribute(results));
}
在实践中我们可能想在该测试结果上实现特定的商业测试例如假定结果属性是一个List它包含一列约个Hotel域对象并且我们想要保证所有在该列表中的宾馆都在法国为了实现这种类型的测试代码将非常相似于标准JUnit测试
public void testSearchByCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country FR);
actionPerform();
verifyNoActionErrors();
verifyForward(success);
assertNotNull(requestgetAttribute(results));
List results = (List) requestgetAttribute(results);
assertEquals(resultssize() );
for (Iterator iter = erator();
iterhasNext();) {
Hotel hotel = (Hotel) iternext();
assertEquals(hotelgetCountry TestConstantsFRANCE);
}
}
当你测试更复杂的情形时你可能想要测试系列化的行为例如假定用户在法国查询所有的宾馆并且点击一个入口来显示相应的查询细节假定我们有一个Struts行为来显示一个给定宾馆的细节信息可以作如下调用
/displayDetailsdo?id=
通过使用StrutsTestCase我们能够容易地在相同的测试情形下模仿一系列的行为一个用户在法国查询所有的宾馆然后点击一个入口来显示相应的查询细节
public void testSearchAndDisplay() {
setRequestPathInfo(/searchdo);
addRequestParameter(country FR);
actionPerform();
verifyNoActionErrors();
verifyForward(success);
assertNotNull(requestgetAttribute(results));
List results = (List) requestgetAttribute(results);
assertEquals(resultssize());
Hotel hotel = (Hotel) resultsget();
setRequestPathInfo(/displayDetailsdo);
addRequestParameter(id hotelgetId());
actionPerform();
verifyNoActionErrors();
verifyForward(success);
Hotel hotel = (Hotel)requestgetAttribute(hotel);
assertNotNull(hotel);
}
四测试Struts错误处理
测试错误处理也是一件很重要的事情假定如果指定一个无效的国家代码时我们想要检查应用程序仍然运行良好为此我们可以写一个新的测试方法并且使用verifyActionErrors()检查返回的Struts ErrorMessages:
public void testSearchByInvalidCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country XX);
actionPerform();
verifyActionErrors( new String[] {errorunknowncountry});
verifyForward(failure);
}
有时你想直接在ActionForm对象中进行数据校验为此你可以使用getActionForm()如下所示:
public void testSearchByInvalidCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country XX);
actionPerform();
verifyActionErrors( new String[] {errorunknowncountry});
verifyForward(failure);
SearchForm form = (SearchForm) getActionForm();
assertEquals(Scott formgetCountry(XX));
}
在此我们可以确保在出现错误后无效的国家代码被正确地存储在ActionForm中
五定制测试环境
重载setUp()方法有时是很有用的它让你指定非缺省的配置选项在这个例子中我们使用一个不同的strutsconfigxml文件并且不激活XML配置文件校验:
public void setUp() {
supersetUp();
setConfigFile(/WEBINF/mystrutsconfigxml);
setInitParameter(validatingfalse);
}
六第一级性能测试
测试一个行为或一系列的行为是一个十种优秀的测试方式它要求能够存取响应次数从Struts行为中进行测试允许你校验全局的服务器端性能(当然除去产生JSP页面)为了尽快隔离和移除性能问题以及把它们集成到构建过程中以帮助避免性能回退在单元测试级上进行一些第一级性能测试是个很不错的注意
下面是我用来进行第一级Struts性能测试的基本原则:
·用尽可能多的组合来测试多条件搜索查询(为了检查这些索引已被正确定义了)
·测试大容量的查询(返回大量结果的查询)来检查响应次数和结果页面(如果使用的话)
·测试单个的和重复的查询(来检查缓沖性能如果使用缓沖策略的话)
有一些开源库可以用于帮助进行性能测试例如由Mike Clark维护的JUnitPerf然而把它们集成到StrutsTestCase中可能有些复杂在很多情况下一个简单的定时器即可以实现这一功能下面是一种简单而有效的实现第一级性能测试的方法
public void testSearchByCountry() {
setRequestPathInfo(/searchdo);
addRequestParameter(country FR);
long t = SystemcurrentTimeMillis();
actionPerform();
long t = SystemcurrentTimeMillis() t;
logdebug(Country search request processed in + t + ms);
assertTrue(Country search too slow t >= )
}
七结论
一般地单元测试是进行灵敏编程特别是基于测试开发的一个基本部分StrutsTestCase为我们提供一种容易并且有效的方法来单元测试Struts行为否则如果使用JUnit来进行单元测试则相当困难