Struts2+Spring + hibernate 中对action的单元测试环境搭建

1.途中碰到的问题。

  • 如何来测试action?网上给出的很多参考使用MockStrutsTestCase,而且还是对应struts1.x的。在apache上查看struts2的文档时候发现,有提供一个包struts2-junit-plugin-2.1.8.jar,里面有测试struts2.x中action的类StrutsSpringTestCase,可以用来测试ssh中的action。
  • 如何来解决JPA中的lazy机制?大家知道,ssh集成后,hibernate的lazy机制成为让人头疼的问题,网上普遍有两种解决方案,在web.xml中使用spring提供的 OpenSessionInViewFilter来实现,还有一种是在application.xml中配置OpenSessionInViewFilter来实现。前者通用,后者只能用在springmvc结构中。

        这里不再详细描述,详见网友blog(http://blog.csdn.net/zxq1406spys/archive/2009/10/30/4748283.aspx)

  • 如何来做用户session管理?比如测试跟登陆用户身份信息相关的action。在StrutsSpringTestCase中有request属性,所以我们可以mock出session来代替网页请求的真实session。

2.上述问题解决以后,我们的单元测试环境就可以开始搭建了。

    环境描述:Struts2  + Spring2.5   +  hibernate3 +  junit4

    目的:对后台Action层的函数进行单元测试

    需要的Jar包:junit4.jar(eclipse自带的,在项目路径中导入即可。“properties->Add Library->JUnit->JUnit4”)

                      struts2-junit-plugin-2.1.8.jar、spring-test-2.5.6.jar、spring-context-3.2.0.M2.jar

方法 描叙
executeAction(String)

根据action的URL链接,输出action的结果流数据(结果类型不是SUCCESS,可以是FreeMarker, velocity, JSP等

getActionProxy(String) 生成一个action的代理
injectStrutsDependencies(object) 注入一个依赖对象
getActionMapping(String) 获得ActionMapping
findValueAfterExecute(String) action执行之后,从值栈中取值


属性 描叙
MockHttpServletRequest request requset传递给struts2
MockHttpServletResponse response 用于测试struts2输出
MockServletContext servletContex The servlet context 传递给struts2

  1. package org.apache.struts2;  
  2.   
  3. import org.apache.struts2.dispatcher.mapper.ActionMapping;  
  4.   
  5. import java.util.HashMap;  
  6. import java.io.UnsupportedEncodingException;  
  7.   
  8. import com.opensymphony.xwork2.ActionProxy;  
  9. import com.opensymphony.xwork2.Action;  
  10.   
  11. import javax.servlet.ServletException;  
  12.   
  13. public class StrutsTestCaseTest extends StrutsTestCase {  
  14.     public void testGetActionMapping() {  
  15.         ActionMapping mapping = getActionMapping("/test/testAction.action");  
  16.         assertNotNull(mapping);  
  17.         assertEquals("/test", mapping.getNamespace());  
  18.         assertEquals("testAction", mapping.getName());  
  19.     }  
  20.   
  21.     public void testGetActionProxy() throws Exception {  
  22.         //set parameters before calling getActionProxy  
  23.         request.setParameter("name""FD");  
  24.           
  25.         ActionProxy proxy = getActionProxy("/test/testAction.action");  
  26.         assertNotNull(proxy);  
  27.   
  28.         TestAction action = (TestAction) proxy.getAction();  
  29.         assertNotNull(action);  
  30.   
  31.         String result = proxy.execute();  
  32.         assertEquals(Action.SUCCESS, result);  
  33.         assertEquals("FD", action.getName());  
  34.     }  
  35.   
  36.     public void testExecuteAction() throws ServletException, UnsupportedEncodingException {  
  37.         String output = executeAction("/test/testAction.action");  
  38.         assertEquals("Hello", output);  
  39.     }  
  40.   
  41.     public void testGetValueFromStack() throws ServletException, UnsupportedEncodingException {  
  42.         request.setParameter("name""FD");  
  43.         executeAction("/test/testAction.action");  
  44.         String name = (String) findValueAfterExecute("name");  
  45.         assertEquals("FD", name);  
  46.     }  
  47. }  

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.StrutsSpringTestCase;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import bijian.controller.action.UserAction;

public class UserActionTest  extends StrutsSpringTestCase {
	
	 /*这个函数相当@Before注解的函数,是调用单元测试后的时候,
	    首先会执行的方法。可以在这里面做一些必要的准备工作*/
	 @Override
	 protected void setUp() throws Exception {
	     super.setUp();
	     SessionFactory sessionFactory = lookupSessionFactory(this.request);
	     Session hibernateSession= getSession(sessionFactory);
	     TransactionSynchronizationManager.bindResource(sessionFactory,new SessionHolder(hibernateSession));
	 }
	 private Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
	     Session session = SessionFactoryUtils.getSession(sessionFactory, true);
	     FlushMode flushMode = FlushMode.NEVER;
	     if (flushMode != null) {
	        session.setFlushMode(flushMode);
	     }
	      return session;
	 }
	 private SessionFactory lookupSessionFactory(HttpServletRequest request) {
	     //“sessionFactory”是你spring配置文件(通常是application.xml)中的SessionFactory。
	     //如:org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
	     return (SessionFactory)this.applicationContext.getBean("sessionFactory");
	 }
	 
	 
	  /*这个是单元测试的方法,一个方法测试一个功能,或者一个action*/
	 public void testGetReceiveMessage() throws Exception {		 
	        /*request是类StrutsSpringTestCase的成员变量, 是MockHttpServletRequest对象,在这里mock出来的一个web中的request*/
	     this.request.setParameter("userId", "1");
	        /*我的环境中,返回值是json格式,result将是json格式的一个字符串输入action的地址*/
	     String result =  executeAction("/modules/train/messageAction!getReceiveMessage.action");
	     
	 }		
}

这样关于lazy加载的问题解决。其实这个时限还是参考了Spring中OpenSessionInViewFilter的。

    首先,通过lookupSessionFactory()函数来获取这个测试环境中的org.hibernate.SessionFactory实体对象,其中的applicationContext是org.springframework.context.ApplicationContext的实现org.springframework.context.support.GenericApplicationContext,我们可以通过它的getBean方法来获取你配置文件中配置的SessionFactory。使用注解是org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean,不需要使用注解的时候可以用org.springframework.orm.hibernate.LocalSessionFactoryBean来实现。

    然后,在通过getSession()来获取其中的一个session,获取session的过程类似于java jdk 1.4以后的ThreadLocal类的实现方法,为每一个线程(用户的每一个request请求)提供一个“私有的”session实体类。关于ThreadLocal是干什么的,这里就不多讲了。所以我们在获取Session的时候,将这个session的FlushMode设置为FlushMode.NEVER,这样就可以让Spring来管理这个session,使得hibernate查询结束后,不会关闭session,从而解决lazy加载的问题。

    最后,调用Spring提供的TransactionSynchronizationManager.bindResource()方法将session绑定到该请求的resources中,值得一提的是,其中的resources也是通过ThreadLocal来实现跟线程绑定的。

关于实现web session的问题,很简单,该类提供了一个MockHttpServletRequest成员变量,我们只要mock一个session出来,然后加入到这个request中,就可以实现session的模拟了。示例代码如下:

HttpSession  session = new MockHttpSession();
String sessionId = UUID.randomUUID().toString();
session.setAttribute(ConstParameter.USER_SESSION, sessionId);
//user是一个用户信息的类,你可以根据你的需要自己定义
UserInfor user = new UserInfo();
user.setUserId(1);
user.setName("xxx");
session.setAttribute(ConstParameter.USER_INFO, user);
request.setSession(session);

这样,我们的单元测试功能就可以用了。

注:在文中,需要加载spring配置文件信息,默认加载的路径是你项目的src目录下,文件名默认为“application.xml”,如果你的spring配置文件放在其它的目录下,或者文件的名字跟默认的不相符,那么你需要重载StrutsSpringTestCase类中的getContextLocations()函数。代码如下:

@Override
public String getContextLocations() {
  //返回你项目中spring配置文件所在的目录
  return "/tutorial/action/TestAccountAction-context.xml";
}




你可能感兴趣的:(Struts2+Spring + hibernate 中对action的单元测试环境搭建)