1. Junit测试框架简介[3]
Junit本质上是一套框架,即开发者制定了一套条条框框,遵循这些条条框框的要求编写测试代码,如继承某个类,实现某个接口,就可以用Junit进行自动测试了。
Junit是以两个关键模式来设计的,命令模式(Command Pattern)及集成模式(Composite Pattern):
² 命令模式:利用Junit框架提供的TestCase定义一个子类,在这个子类中可以定义任意数量的testXXX()测试方法,在测试方法中编写代码检测某个方法被调用后对象的状态与预期的状态是否一致。当这个子类包含多个testXXX()方法时,可以使用setUp()及tesrDown()方法建立测试的初始化数据(因为大部分方法的测试数据是相同的),这两个方法如同测试的基础设施,因此叫做测试基础(fixture)。
² 集成模式:利用TestSuite可以将一个TestCase子类中所有testXXX()方法包含进来一起运行,而且它本身也可以被别的TestSuite所包含,从而形成一种层次关系。这样,通过TestSuite就可以组织任意深度的组合测试。
2. Cactus测试框架简介[4]
cactus单元测试工具是对junit框架的扩充,使junit的思想和便利同样用于Browser/Server web应用程序中的测试;同时,利用cactus还可以测试EJB类。实际上,cactus只是提供了测试客户端在容器上的一个代理,它的工作原理如下图所示:
下面分步骤解释在cactus Testcase子类里每一个testXXX()方法的具体执行步骤:
1. JUnit 测试运行器调用YYYTestCase.runTest()方法。这个方法寻找 beginXXX(WebRequest)方法,如果找到则执行。 传给beginXXX(WebRequest)方法的参数WebRequest 可用来设置 HTTP头, HTTP 参数,这些参数将被发送到第2步的 Redirector 代理。
2. YYYTestCase.runTest() 方法打开连向Redirector 代理的HTTP 连接,beginXXX(WebRequest)方法设置的HTTP协议参数将被送到代理。
3. Redirector 代理在服务端作为YYYTestCase的代理(其实YYYTestCase将被实例化两次,一次在客户端被JUnit 测试运行器实例化,一次在服务器端被代理实例化,客户端实例执行beginXXX() and endXXX()方法,服务端实例执行Junit 测试用例的方法setup()、testXXX()and teardown())。
4. 执行Junit 测试用例的方法setup(),testXXX(),and teardown();
5. testXXX()方法调用服务端代码来进行测试,使用assertEquals()方法对测试结果和预期结果进行比较,如果两者相符为测试成功,否则为测试失败;(EJB的问题将在这里解决,因为是在容器环境中执行,因此甚至可以访问EJB的本地接口。)
6. 如果测试失败,Redirector 代理将捕获testXXX()方法抛出的的异常;
7. Redirector 代理将异常信息返回给客户端的JUnit 测试运行器,JUnit 测试运行器可以生成测试报告;
8. 如果没有异常出现, YYYTestCase.runTest()方法寻找endXXX(org.apache.cactus.WebResponse)和endXXX(com.meterware.httpunit.WebResponse) (后者用在和httpunit集成中) 方法,如果找到则执行。endXXX方法中,我们可以检查返回的HTTP 头, Cookies 和output stream ,这个检查可以借助于Junit的 assertEquals或者cactus提供的帮助类。
3. Junit与cactus的安装
Junit的安装:
1. 在http://www.junit.org/index.htm下载最新版本的Junit程序包;
2. 将Junit程序包解压至某一目录下$JUNIT_HOME下,如c:\junit;
3. 将$JUNIT_HOME/junit.jar和$JUNIT_HOME加入到CLASSPATH中,加入后者是因为测试例程在$JUNIT_HOME/junit目录下。
Cactus的安装与配置:
1. 在http://jakarta.apache.org/cactus/downloads.html下载cactus包。
2. 按照下图设置classpath。classpath的设置非常重要,因为90%以上的cactus错误可能都是来自classpath的错误设置。[5]
3. 设置客户端cactus.properties。cactus需要redirector 代理才能工作,所以除了把这些代理考到相应的webapp的类路径(对于filter和servlet代理)或webapp路径(对于jsp代理)外,我们还需要告诉客户端测试实例到哪里去找这些代理。一般的配置如下:
cactus.contextURL=http://localhost:7001/epiccweb
cactus.servletRedirectorName=ServletRedirector
cactus.jspRedirectorName=JspRedirector
cactus.filterRedirectorName=FilterRedirector
4. 设置web.xml,加入以下代码
<filter>
<filter-name>FilterRedirector</filter-name>
<filter-class>org.apache.cactus.server.FilterTestRedirector</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterRedirector</filter-name>
<url-pattern>/FilterRedirector</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>ServletRedirector</servlet-name>
<servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class>
</servlet>
<servlet>
<servlet-name>JspRedirector</servlet-name>
<jsp-file>/jspRedirector.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>ServletRedirector</servlet-name>
<url-pattern>/ServletRedirector</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>JspRedirector</servlet-name>
<url-pattern>/JspRedirector</url-pattern>
</servlet-mapping>
5. 如何编写测试类
目录结构及命名规则
在介绍如何编写测试类之前,先介绍一下测试类的存放位置。一般来说,测试类与被测试的类具有相同的包结构,在某一模块中,测试类和被测试类分别存放在src/和test/目录下,如右图所示。
另外,统一规定测试类名为被测试类名前加test,测试方法名为被测试方法名前加test。
建立测试类
在本项目中,单元测试将涉及到三方面类的测试:普通Java类、EJB、Servlet。针对这三种不同的类,这里给出不同的解决方式。
1. 普通Java类的测试
以com.picc.epicc.util.pub包中的StrHandler类为例,为测试decode方法,建立testStrHandler如下:
package com.picc.epicc.util.pub;
import junit.framework.*;
import com.picc.epicc.util.pub.*;
import java.util.*;
public class testStrHandler extends TestCase
{
public testStrHandler(String name) {
super(name);
}
protected void setUp() {
}
protected void tearDown() {
}
public void testDecode()
{
StrHandler strHandler = new StrHandler();
String testStr = "haha|hehe|heihei|hoho|";
Collection testList = new Vector();
strHandler.decode(testStr,testList);
//检验
assertEquals(testList.size(),4);
Iterator it = testList.iterator();
String hahaStr = (String)it.next();
assertEquals(hahaStr,"haha");
String hohoStr = "" ;
while(it.hasNext())
hohoStr = (String)it.next();
assertEquals(hohoStr,"hoho");
}
}
2. EJB的测试
由于EJB的测试需要依赖EJB容器环境,因此稍微复杂。尤其对于实体Bean的测试,由于实体Bean只实现本地接口(在本项目中,有些普通JavaBean由于涉及到对实体Bean的调用,如DtoFactory、Action等,因此也需在容器内测试),因此如果利用普通的客户端测试,还需为实体Bean建立远程接口,这将是一件非常繁冗的工作。利用cactus在容器中运行的特性,可以实现EJB的测试,实现如下:
以com.picc.epicc.util.bl.action. CodeAction为例,建立testCodeAction如下:
package com.picc.epicc.util.bl.action;
import junit.framework.*;
import org.apache.cactus.*;
import com.picc.epicc.util.bl.action.*;
import com.picc.epicc.util.dto.domain.*;
import java.util.*;
public class testCodeAction extends ServletTestCase
{
public testCodeAction(String name) {
super(name);
}
protected void setUp() {
}
protected void tearDown() {
}
public void testGetDataByPK() throws Exception
{
CodeAction codeAction = new CodeAction();
EbsDcompanyDto ebsDcompanyDto = (EbsDcompanyDto)codeAction.getDataByPK("Dcompany","11019398");
assertEquals(ebsDcompanyDto.getComtype(),"2");
}
public void testGetData() throws Exception
{
CodeAction codeAction = new CodeAction();
Collection ebsDcompanyDtos = codeAction.getData("Dcompany","comcode='11019398'");
Iterator it = ebsDcompanyDtos.iterator();
if(it.hasNext())
{
EbsDcompanyDto ebsDcompanyDto = (EbsDcompanyDto)it.next();
System.out.println("comtype"+ebsDcompanyDto.getComtype());
assertEquals(ebsDcompanyDto.getComtype(),"2");
}
}
}
3. Servlet的测试
Servlet测试与EJB测试方法类似,这里不再赘述。需要提出的是:在cactus中,可以在beginXXX()方法中准备Http Request数据,如下所示:
public void beginAddUser(WebRequest theRequest)
{
theRequest.addParameter("name", "nameValue");
theRequest.addParameter("pass","passValue") ;
theRequest.addParameter("tel","telValue") ;
}
另外,由于对Servlet的测试是实例化Servlet,容器不会自动调用其init()方法,因此需要在setup()方法中显式调用Servlet的init()方法。
6. Junit与ANT的结合
通过与ANT工具的集成,将实现测试的自动化, 下面分别阐述Junit工具与cactus工具与ANT的结合。
1. Junit与ANT的结合:
<target name="testPub" depends="cpTest">
<java fork="yes" classname="junit.textui.TestRunner" failonerror="true">
<arg value="com.picc.epicc.util.pub.testAll" />
<classpath>
<pathelement location="${env.APPLIB}/util.jar" />//被测试类
<pathelement location="${env.SYSLIB}/junit.jar" />
<pathelement location="${env.TESTLIB}/testUtil.jar" />//测试类
<pathelement path="" />
<pathelement path="${java.class.path}" />
</classpath>
</java>
</target>
或者:
<target name="testPub" depends="cpTest">
<junit printsummary="yes" fork="yes" haltonfailure="no" >
<classpath>
<pathelement location="${env.APPLIB}/util.jar" />
<pathelement location="${env.SYSLIB}/junit.jar" />
<pathelement location="${env.TESTLIB}/testUtil.jar" />
<pathelement path="" />
<pathelement path="${java.class.path}" />
</classpath>
<formatter type="xml"/>
<test name="com.picc.epicc.util.pub.testStrHandler" haltonfailure="no" outfile="testPub" todir="../test/report/xml" />
</junit>
<junitreport todir="../test/report/xml">
<fileset dir="../test/report/xml">
<include name="testPub.xml"/>
</fileset>
<report format="noframes" todir="../test/report/html"/>
</junitreport>
</target>
2. cactus与ANT的结合
<target name="testAction" depends="cpTest">
<java fork="yes" classname="junit.textui.TestRunner" failonerror="true">
<arg value="com.picc.epicc.util.bl.action.testCodeAction" />
<classpath>
<pathelement location="${env.SYSLIB}/junit.jar" />
<pathelement location="${env.SYSLIB}/cactus-1.5-beta1.jar" />
<pathelement location="${env.SYSLIB}/aspectjrt-1.0.6.jar" />
<pathelement location="${env.SYSLIB}/commons-httpclient-2.0-beta2.jar" />
<pathelement location="${env.SYSLIB}/commons-logging-1.0.3.jar" />
<pathelement location="${env.TESTLIB}/testUtil.jar" />
<pathelement location="${env.USERCONFIG}" />//cactus.properties的位置
<pathelement path="" />
<pathelement path="${java.class.path}" />
</classpath>
</java>
</target>