用Cactus来测试J2ee应用
Java 专区中还有:
教学
工具与产品
代码与组件
所有文章
实用技巧
韩伟 (java_cn@21cn.com)
北京某公司系统分析员
2002 年 8 月
Junit是当前最流行的测试框架,它能够让开发人员很方便的编写测试单元,可以使他们"放心"地开发。但是现在很多的应用都是基于j2ee的,代码都是在服务器端的容器里面运行,这个使测试带来了一些麻烦。对于普通的jsp,servlet用Junit来测试好像已经不是那么方便,对于EJB来说,特别是2.0版本,很多接口都是Local Interface,没有办法进行分布式的测试。那么我们如何进行这些代码的测试呢?Apache为我们提供了一个强大的工具 Cactus!它是一套简单,易于使用的服务器端测试框架,可以使开发人员很轻松的测试服务器端的程序,他们会说:"哦,就是这么简单"。Cactus是Junit的一个扩展,但是它又和Junit有一些不同。Cactus的测试分为三种不同的测试类别,JspTestCase,ServletTestCase,FilterTestCase,而不是像Junit就一种TestCase。Cactus的测试代码有服务器端和客户端两个部分,他们协同工作。那我们为什么不用Junit来做测试呢?主要有一下几个理由:
EJB2.0中的Local interface ,不允讯远程调用。用Junit不好测试,而Cactus的redirector位于服务器端,可以和EJB运行在一个容器中,这使得它可以直接访问Local Interface。
一般EJB或者servlet,jsp都是运行在服务器上,如果你使用junit测试的话,你的测试是在客户端,这使的运行环境和测试环境处于不同的系统环境中,这个有时候会不同的测试结果。
在一个EJB的应用中,一般都会有一些前端应用来访问EJB,例如:jsp,servlet,javabean。这就意味着你需要一个测试框架来测试这些前端的组件。Cactus提供了所有这些组件的测试方法。哦,太棒了。
Cactus和ant很好的结合在一起,可以很容易的完成自动化测试,减少了很多工作量。当然,junit也提供这样的支持。
前面是对Cactus作了一个大致的介绍,接下来我们用一个实际的例子来运用一下这个强大的测试框架。首先我们需要一个被测试的对象,在这里我们选用EJB2.0 CMP.我们做一个简单的用户管理。一下就一些主要的代码,来进行一些分析。
UserHome.java
package usersystem;
import javax.ejb.*;
import java.util.*;
public interface UserHome extends javax.ejb.EJBLocalHome {
public User create(String name, String password) throws CreateException;
public Collection findAll() throws FinderException;
public User findByPrimaryKey(String name) throws FinderException;
}
User.java
package usersystem;
import javax.ejb.*;
import java.util.*;
public interface User extends javax.ejb.EJBLocalObject {
public String getName();
public void setPassword(String password);
public String getPassword();
public void setUserInfo(UserInfo userInfo);
public UserInfo getUserInfo();
public void setName(String name);
}
UserInfoHome.java
package usersystem;
import javax.ejb.*;
import java.util.*;
public interface UserInfoHome extends javax.ejb.EJBLocalHome {
public UserInfo create(String name, String email, String address, String tel) throws
CreateException;
public UserInfo findByPrimaryKey(String name) throws FinderException;
}
这里有两个Entity Bean用来创建用户信息。他们之间的关系在xml部署描述文件中描述,他们是1对1的关系。
UserManagerLocal.java
package usersystem;
import javax.ejb.*;
import java.util.*;
public interface UserManagerLocal extends javax.ejb.EJBLocalObject {
public void addUser(String name, String password, String email, String address, String tel);
public Collection findAll() ;
public void delAll();
public void delByName(String name);
public User findByName(String name) ;
}
UserManagerBean.java
package usersystem;
import javax.ejb.*;
import javax.rmi.PortableRemoteObject;
import javax.naming.*;
import java.util.*;
public class UserManagerBean implements SessionBean {
SessionContext sessionContext;
public void ejbCreate() throws CreateException {
/**@todo Complete this method*/
}
public void ejbRemove() {
/**@todo Complete this method*/
}
public void ejbActivate() {
/**@todo Complete this method*/
}
public void ejbPassivate() {
/**@todo Complete this method*/
}
public void setSessionContext(SessionContext sessionContext) {
this.sessionContext = sessionContext;
}
/**
* 添加用户
* @param name 用户姓名
* @param password 密码
* @param email 电子邮件
* @param address 地址
* @param tel 电话
*/
public void addUser(String name, String password, String email, String address, String tel) {
try{
UserHome userHome=getUserHome();
User user=userHome.create(name,password) ; //create user entity
UserInfoHome userInfoHome=getUserInfoHome();
UserInfo userInfo=userInfoHome.create(name,email,address,tel) ;// create userinfo
entity
user.setUserInfo(userInfo) ;
}catch(Exception e){
throw new javax.ejb.EJBException (e.toString());
}
}
/**
* 返回UserHome接口
* @return userHome
*/
private UserHome getUserHome(){
try {
javax.naming.InitialContext ctx=new javax.naming.InitialContext ();
Object ref = ctx.lookup("User");
//cast to Home interface
UserHome userHome = (UserHome) PortableRemoteObject.narrow(ref, UserHome.class);
return userHome;
}
catch (ClassCastException ex) {
ex.printStackTrace() ;
return null;
}catch (NamingException ex) {
ex.printStackTrace() ;
return null;
}
}
/**
* 返回UserInfoHome接口
* @return
*/
private UserInfoHome getUserInfoHome(){
try {
javax.naming.InitialContext ctx=new javax.naming.InitialContext ();
Object ref = ctx.lookup("UserInfo");
//cast to Home interface
UserInfoHome userInfoHome = (UserInfoHome) PortableRemoteObject.narrow(ref,
UserInfoHome.class);
return userInfoHome;
}
catch (ClassCastException ex) {
throw new EJBException();
}catch (NamingException ex) {
throw new EJBException(ex.toString());
}
}
/**
* 返回所有用户记录
* @return c
* @throws javax.ejb.FinderException
*/
public java.util.Collection findAll() {
Collection c = null;
try {
UserHome uh=this.getUserHome() ;
c=uh.findAll() ;
}
catch (FinderException ex) {
throw new javax.ejb.EJBException ();
}
return c;
}
/**
* 删除所有记录
*/
public void delAll(){
try {
UserHome u=getUserHome();
java.util.Collection c=u.findAll() ;
java.util.Iterator i=c.iterator() ;
while(i.hasNext() ){
u.remove(((User)i.next()).getName()) ;
}
}
catch (Exception ex) {
throw new EJBException(ex.toString());
}
}
/**
* 根据用户名删除记录
* @param name
*/
public void delByName(String name) {
try {
User user=findByName(name);
UserHome uh=getUserHome();
uh.remove(user.getName()) ;
}
catch (Exception ex) {
throw new javax.ejb.EJBException (ex.toString());
}
}
/**
* 通过用户名查找用户记录
* @param name
* @return
*/
public User findByName(String name) {
try {
UserHome uh=this.getUserHome() ;
User user=(User)uh.findByPrimaryKey(name) ;
UserHome u=this.getUserHome() ;
User uu=u.findByPrimaryKey(name) ;
return user;
}
catch (FinderException ex) {
throw new EJBException(ex.toString());
}
}
}
UserManagerBean是一个session bean ,它主要是对user的管理,和客户端通讯,其实就是session facade模式 。代码里面有注释,这里就不多叙述了。
ejb-jar.xml 部署文件描述
"">
UserManager
UserManager
usersystem.UserManagerLocalHome
usersystem.UserManagerLocal
usersystem.UserManagerBean
Stateless
Container
User
Entity
usersystem.UserHome
usersystem.User
User
UserInfo
Entity
usersystem.UserInfoHome
usersystem.UserInfo
UserInfo
User
User
usersystem.UserHome
usersystem.User
usersystem.UserBean
Container
java.lang.String
False
2.x
User
name
password
name
findAll
select Object(theUser) from User as theUser
UserInfo
UserInfo
usersystem.UserInfoHome
usersystem.UserInfo
usersystem.UserInfoBean
Container
java.lang.String
False
2.x
UserInfo
name
email
address
tel
name
userInfo-user
userInfo
UserInfoRelationshipRole
One
userInfo
UserInfo
user
user
user
UserRelationshipRole
One
user
User
userInfo
userInfo
User
*
Required
UserManager
*
Required
UserInfo
*
Required
接下来是访问EJB的客户端,我们用了一个servlet.
ManaServlet.java
package usersystem.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import usersystem.*;
import javax.naming.*;
import javax.ejb.*;
import javax.ejb.*;
import javax.ejb.*;
/**
* Title:
* Description:
* Copyright: Copyright 2002
* Company:
* @author unascribed
* @version 1.0
*/
public class ManaServlet extends HttpServlet {
static final private String CONTENT_TYPE = "text/html; charset=GBK";
private UserManagerLocalHome h=null;
private UserManagerLocal uml=null;
public void init() throws ServletException{
try {
h=getHome();
uml=h.create() ;
}
catch (CreateException ex) {
ex.printStackTrace() ;
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
}
public void addUser(HttpServletRequest request, HttpServletResponse response) throws
javax.ejb.EJBException {
String name=request.getParameter("name") ;
String tel=request.getParameter("tel") ;
String address=request.getParameter("address") ;
String email=request.getParameter("email") ;
String pass=request.getParameter("pass") ;
uml.addUser(name,pass,email,address,tel) ;
}
public User findByName(String name) throws javax.ejb.EJBException {
User u = null;
u=uml.findByName(name) ;
return u;
}
public java.util.Iterator findAll() throws javax.ejb.EJBException {
java.util.Collection c=uml.findAll() ;
return c.iterator() ;
}
public void delAll() throws javax.ejb.EJBException {
uml.delAll() ;
}
public void delUser(String name) throws javax.ejb.EJBException {
uml.delByName(name) ;
}
public UserManagerLocalHome getHome() {
UserManagerLocalHome home = null;
try {
javax.naming.InitialContext ctx=new javax.naming.InitialContext ();
home=(UserManagerLocalHome)ctx.lookup("UserManagerLocal") ;
}
catch (NamingException ex) {
ex.printStackTrace() ;
return null;
}
return home;
}
public void destroy() {
}
}
这个servlet在doGet,doPost没有实现任何方法,这个不影响我们测试,我们要测试的只是这些public method. 我们的测试代码如下:
package usersystem.test;
/**
* Title:
* Description:
* Copyright: Copyright 2002
* Company:
* @author unascribed
* @version 1.0
*/
import usersystem.servlet.*;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.Hashtable;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.cactus.Cookie;
import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;
import org.apache.cactus.WebResponse;
import javax.ejb.*;
import javax.servlet.*;
import usersystem.*;
public class ManaServletTest extends ServletTestCase{
ManaServlet servlet=new ManaServlet();
public ManaServletTest(String theName) {
super(theName);
}
public void setUp(){
try {
servlet.init() ;
}
catch (ServletException ex) {
ex.printStackTrace() ;
this.fail() ;
}
}
public void tearDown(){
}
public void beginAddUser(WebRequest theRequest)
{
theRequest.addParameter("name", "nameValue");
theRequest.addParameter("pass","passValue") ;
theRequest.addParameter("tel","telValue") ;
theRequest.addParameter("address","addressValue") ;
theRequest.addParameter("email","emailValue");
}
public void testAddUser() throws javax.ejb.EJBException{
servlet.addUser(request,response) ;
}
public void testFindAll(){
java.util.Iterator i=servlet.findAll() ;
//assertEquals(null,i);
boolean ok=false;
while(i.hasNext() ){
if(((User)i.next()).getName().equals("nameValue")) {
ok=true;
};
}
this.assertTrue(ok) ;
}
public void testFindByName() throws javax.ejb.EJBException {
User u=servlet.findByName("nameValue") ;
UserInfo ui=u.getUserInfo() ;
this.assertEquals("email",ui.getEmail()) ;
this.assertEquals("tel",ui.getTel()) ;
this.assertEquals("nameValue",u.getName()) ;
this.assertEquals("passValue",u.getPassword()) ;
}
public void testDel() throws javax.ejb.EJBException {
servlet.delUser("nameValue8") ;
}
public void testDelAll() throws javax.ejb.EJBException {
servlet.delAll() ;
}
public static void main(String[] theArgs)
{
junit.textui.TestRunner.main(new String[]{
ManaServletTest.class.getName()});
}
public static Test suite()
{
return new TestSuite(ManaServletTest.class);
}
}
public class ManaServletTest extends ServletTestCase 我们要测试的是一个servlet,所以我们继承ServletTestCase,如果你测试jsp的话,就继承JspTestCase.
public ManaServletTest(String theName) {
super(theName);
}
和junit一下,ServletTestCase不允许使用默认的构造函数,所以必须使用一个带参数的构造函数,并且调用 父类的构造函数。
public void setUp(){
try {
servlet.init() ;
}
catch (ServletException ex) {
ex.printStackTrace() ;
this.fail() ;
}
}
public void tearDown(){
}
setUp是在测试类运行时候首先被调用的办法,在这里可以进行一些数据初始化之类的工作。在这里我们调用了 servlet.init().
在测试类运行的时候需要显式的调用servlet的init()方法。因为cactus在测试servlet的时候是实例化一个ser vlet的,不会调用inti(),而servlet enginer在调用的时候是会自动调用servlet的init()方法的。tearDown方 法在测试完成的时候运行,进行一些必要的数据处理,比如删除一些测试数据等,这里我们没有做任何工作。
public void beginAddUser(WebRequest theRequest)
{
theRequest.addParameter("name", "nameValue");
theRequest.addParameter("pass","passValue") ;
theRequest.addParameter("tel","telValue") ;
theRequest.addParameter("address","addressValue") ;
theRequest.addParameter("email","emailValue");
}
public void testAddUser() throws javax.ejb.EJBException{
servlet.addUser(request,response) ;
}
在Cactus中,你需要用testXXX来命名你的方法,这样Cactus会自动调用这个方法进行测。而BeingXXX则是在调 用test方法之前调用,也就是说在一个功能测试之前运行。这里我们现在beginAddUser中添加一些必要的参数 。WebRequest是Cactus提供的一个类,它允许你设置一些Http参数,如果你使用了 theRequest.addParameter("name","nameValue"),那么在servlet中你就可以用request.getParameter("name") 来取得name的值。当然还可以设置Cookie,Http Head参数。在testAddUser()方法中我们测试addUser方法,如 果测试有异常,则会产生EJBException,得到一个测试失败。
public void testFindByName() throws javax.ejb.EJBException {
User u=servlet.findByName("nameValue") ;
UserInfo ui=u.getUserInfo() ;
this.assertEquals("email",ui.getEmail()) ;
this.assertEquals("tel",ui.getTel()) ;
this.assertEquals("nameValue",u.getName()) ;
this.assertEquals("passValue",u.getPassword()) ;
}
这个测试是测试根据用户名查找用户,之后你可以用assertEquals方法来测试返回的值是否正确。
public static void main(String[] theArgs)
{
junit.textui.TestRunner.main(new String[]{
ManaServletTest.class.getName()});
}
这里我们使用textui来运行我们的测试类,提供文本的测试信息,还有一个Swing的测试方法,一共一个界面, 但是没有什么太大的意义。
到此我们介绍了所有的主要方法。最后我们谈谈如何运行这个测试。
首先下载Cactus。
把lib/下的jar文件加入到 web app的lib下。以及你客户端的classpath中,这是最保险的,虽然不是所有 的jar都用的着。
设置你的Cactus.找到cactus.properties 文件,把它加入到客户端的classpath中。
修改cactus.properties 文件,把 改成你相应的设置,test是你web应用的 名称。其他设置可以不变。
修改服务器端web应用的配置,在web.xml中加入:
FilterRedirector
org.apache.cactus.server.FilterTestRedirector
FilterRedirector
/FilterRedirector
ServletRedirector
org.apache.cactus.server.ServletTestRedirector
JspRedirector
/jspRedirector.jsp
ServletRedirector
/ServletRedirector
JspRedirector
/JspRedirector
编译ejb和servlet,把EJB文件的jar,和servlet的war文件打包成 ear文件。
发布你的ear文件到web application.
运行本地的测试文件ManaServletTest.class
哈哈~~,终于完成了所有的工作,我们可以看看运行结果,"哦,不",居然出现了一个Error,那就是你的程序出现了问题,仔细看看吧,测试是不会骗你的 :) 。 以上代码在 win2000+JBOSS3.0+MySql MAX 3.24+Cactus1.3上运行成功。
关于作者
韩伟,任北京某公司系统分析员,主要从事j2ee发面的开发,对设计模式,Java,软件工程很感兴趣。您可以通过email:java_cn@21cn.com跟他取得联系。
阅读(702) | 评论(0) | 转发(0) |