不好意思,最近由于在赶项目所以这篇文章今天才有时间写出来
首先讲讲taglib的使用目的,只有明确的使用目的我们才能写出明确的单元测试
通常我们自定义的taglib都是为了根据一些参数达到我们需要view层样式,在我的项目中一般比较少的使用自定义标签的body形式(body一般是为了通过标签达到框架级的页面结构),因此,对于一个taglib来说它一般要做事情有:
1、获取参数
2、根据参数获取结果集(通常这个主要是bl层的任务)
3、根据结果集得到输出样式(得到的样式一般都是一个html或者wml的字符串)
4、把得到的输出样式最终输出到页面上
根据上面的分析其实我们可以看出我们需要把测试的焦点集中在3上,因为其它的任务主要是通过调用其它封装好的方法来实现的。
用一个实例来介绍一下我的做法吧:ShowCatalogTag,主要是根据传递的类别id,显示相关的类别信息和子类信息。
根据需求我们不难看出这个标签的主要功能是
1、获取类别ID和相关样式显示参数
2、根据类别id获取类别相关信息和子类别信息
3、根据类别信息结果集和显示参数得到输出wml代码
4、把类别样式最终输出到页面上
根据需求分析我们可以设计出我们标签的主要方法有:
getOutWml()的到最终的wml输出
getCatalogLayout(List catalogList)根据类别结果集得到类别样式
然后,根据上述设计,我们可以首先写我们的单元测试了
单元测试一般是从最底层的实现方法开始写起,所以我们首先写testGetCatalogLayout
1
public
void
testGetCatalogLayout()
throws
Exception
{
4
List catalogList=new ArrayList();
5
CsCatalog testcatalog=new CsCatalog();
6
testcatalog.setCatalogName("ring");
7
testcatalog.setId(23l);
8
catalogList.add(testcatalog);
9
//得到待测方法结果
12
StringBuffer result = sct.getCatalogLayout(catalogList);
13
logger.debug(result);
14
//设置期望结果
15
StringBuffer outPut = new StringBuffer();
16
if (null != catalogList && catalogList.size() != 0)
{
17
CsCatalog catalog = (CsCatalog) catalogList.get(0);
18
Map parameterMap = new LinkedMap();
19
for (int i = 1; i < catalogList.size() - 1; i++)
{
20
21
catalog = (CsCatalog) catalogList.get(i);
22
parameterMap = new LinkedMap();
23
parameterMap.put("catalogid", Long.toString(catalog.getId()));
24
outPut.append(catalog.getCatalogName());
25
}
26
}
27
//进行断言判断期望和实际结果
28
assertEquals(outPut.toString(),result.toString());
29
}
此时,有关getCatalogLayout的测试方法已经写完了,但是实际上这个方法我们还没有写呢。所以在eclipse中会显示错误我们使用eclipse的自动完成功能来在标签中实现一个空getCatalogLayout方法,下面我将写getCatalogLayout方法的实现 :
public
StringBuffer getCatalogLayout(List catalogList)
{
StringBuffer outPut = new StringBuffer();

if (null != catalogList && catalogList.size() != 0)
{
CsCatalog catalog = (CsCatalog) catalogList.get(0);
Map parameterMap = new LinkedMap();

for (int i = 1; i < catalogList.size() - 1; i++)
{

catalog = (CsCatalog) catalogList.get(i);
parameterMap = new LinkedMap();
parameterMap.put("catalogid", Long.toString(catalog.getId()));
outPut.append(catalog.getCatalogName());
}
}
return outPut;
}
然后运行eclipse的run->junit test,ok,我们期待的绿条来了,如果要是红条,那么你就需要仔细检查一下你的方法实现代码了:)
上面的方法其实主要是说了一下如何用tdd的方式来思考和编写代码,其实getCatalogLayout这个方法基本上是和标签环境隔离的,需要传递的参数只有已知的cataloglist
下面将介绍一下在标签中使用到相关环境时的解决方案
在标签中我们一般需要使用的外部环境参数主要就是pageContext,而在pageContext中有我们需要的request,webapplicationcontext,response等等环境变量。
因此要进行完整的标签单元测试就必须要考虑到把加入相关的环境变量
首先考虑加载spring的WebApplicationContext,前一篇文章讲DAO单元测试的时候我提到过用springmock来加载spring的上下文,但是springmock加载的是ClassPathApplicationContext,两者是不一样的,而我查找了资料后没有找到相关的转换方法,结果我只有通过配置文件的路径得到WebApplicationContext,下面是一个工具类用于对spring的相关信息进行加载
/** */
/**
* $Id:$
*
* Copyright 2005 easou, Inc. All Rights Reserved.
*/
package
test.spring.common;

import
javax.servlet.ServletContext;
import
javax.servlet.ServletContextEvent;
import
javax.servlet.ServletContextListener;

import
org.springframework.mock.web.MockPageContext;
import
org.springframework.mock.web.MockServletContext;
import
org.springframework.web.context.ContextLoader;
import
org.springframework.web.context.ContextLoaderListener;
import
org.springframework.web.context.WebApplicationContext;

import
test.PathConfig;

import
com.easou.commons.web.taglib.BaseTag;


public
class
SpringTestUtil
{


/** *//**
* @author rocket 初始化tag所需的MockServletContext
*
*/

protected static MockServletContext getSpringMockSC()
{
MockServletContext mockServletContext = new MockServletContext();
mockServletContext.addInitParameter(
ContextLoader.CONFIG_LOCATION_PARAM, PathConfig
.getStringXmlPath(PathConfig.springxml));
ServletContextListener listener = new ContextLoaderListener();
ServletContextEvent event = new ServletContextEvent(mockServletContext);
listener.contextInitialized(event);
return mockServletContext;

}


/** *//**
* @author rocket 针对tag设置Spring的上下文
* @param tag
*/

public static void setSpringContextInTag(BaseTag tag)
{

MockPageContext mockPC = new MockPageContext(getSpringMockSC());
tag.setPageContext(mockPC);
}


/** *//**
* @author rocket 获得spring的上下文
* @return spring的上下文
*/

public static WebApplicationContext getSpringContext()
{
MockServletContext mockServletContext = getSpringMockSC();
return (WebApplicationContext) mockServletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

}


/** *//**
* @author rocket 对servletContext设置spring的上下文
* @param servletContext
*/

public static void setSpringContext(ServletContext servletContext)
{

servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
getSpringContext());
}

}
这个类中的几个方法可以对mock的pagecontext或者servletcontext加载spring的上下文
这里简单介绍一下我使用的环境模拟工具:
我没有使用流行的mockObject和MockRunner,主要是因为我的项目基本框架是spring+Struts,而对这两个框架的模拟有更有针对性地springmock和strutstestcase,这样在我需要加载struts相关配置信息时,strutstestcase提供了更好的支持。
比如我这里需要使用到strust中配置好的properties信息,那么下面各测试基类就基本实现的我的要求
/** */
/**
* $Id:$
*
* Copyright 2005 easou, Inc. All Rights Reserved.
*/
package
test.struts.common;

import
org.springframework.mock.web.MockPageContext;
import
org.springframework.transaction.PlatformTransactionManager;
import
org.springframework.transaction.TransactionStatus;
import
org.springframework.transaction.support.DefaultTransactionDefinition;
import
org.springframework.web.context.WebApplicationContext;

import
servletunit.struts.MockStrutsTestCase;
import
test.spring.common.SpringTestUtil;

import
com.easou.commons.web.taglib.BaseTag;


public
class
BaseStrutsTestCase
extends
MockStrutsTestCase
{


/** *//** The transaction manager to use */
protected PlatformTransactionManager transactionManager;


/** *//**
* TransactionStatus for this test. Typical subclasses won't need to use it.
*/
protected TransactionStatus transactionStatus;


/** *//**
* @author rocket 设施struts配置文件的路径
*
*/

protected void setUp() throws Exception
{
super.setUp();