当前位置: 代码迷 >> 驱动开发 >> Struts2 Spring hibernate with JUnit 测试驱动开发(1)
  详细解决方案

Struts2 Spring hibernate with JUnit 测试驱动开发(1)

热度:577   发布时间:2016-04-28 10:43:33.0
Struts2 Spring hibernate with JUnit 测试驱动开发(一)

????? 项目中没有测试用例给人最头疼的就是不好重构,甚至不敢重构,我现在所参与的一个B2B项目从开始到现在压根没有任何一个测试用例,甚至都没有重构过,看到有人一个方法2000行,心里都在发颤:如果那家伙离职了,他的代码的维护就是件非常头痛的事情,与其维护,不如重写算了。。然而B2B项目经常需要适应用户去做相应的改动,所以没到这个时候,就是我最害怕的时候,改完了,发布上线,就要经历那心惊肉跳的稳定期。现在维护一个B2B项目的同时,又要开始另外B2C项目的开发,不想重蹈覆辙,这次干脆在项目一开始时候就搭建测试框架。这次主要介绍,如何在struts2,spring,hibernate整合的时候,使用junit来测试struts2的action。

?

??????首先,本示例用到的类如下:BaseAction,PersonalAction,PersonalService,

B2cUIser,配置文件省略,使用eclipse自带的JUnit3.

?

????? 这个测试,难点就是在于如何测试action中的session,request中所保存的数据,struts2与struts有很大的不同,所以测试方式也有所不同,如果你使用的是struts,那么请参考下面这篇文章? http://dendrobium.iteye.com/admin/blogs/122752。相比之下struts2的action测试要比struts要简单的多,因为struts2的action就是一个普通的java类,没有与servlet API耦合在一起,所以不需要借助其他的类就可以很好的完成测试。

?

????? 下面,让我们看一个测试场景:用户登陆,需要输入用户名,密码和验证码,在用户登陆成功之后,将用户对象保存在session中。

?

代码如下:

BaseAction:

?

public class BaseAction extends ActionSupport{	//获取ActionContext (request)	public ActionContext getActionContext()	{		return ActionContext.getContext();	}	//获取session		public Map getSession()	{		return getActionContext().getSession();	}                //获取application	public Map getServletContext()                 {		return getActionContext().getApplication();	}}

BaseAction中关键的部分就是获取request,session,application的方式,本身struts2提供的ActionContext就是以Map的形式存储结果,然后由拦截器将其中的内容复制给对应的request,session,application中的内容,所以采取这种做法就可以在使用request,session,application的时候不依赖于Servlet API. 如果使用的是ServletActionContext来获取request,sesssion,application的话,这里将无法测试下去(也许你有更好的办法,不如提出来大家一起分享……)

?

index.jsp 登陆页面

?

      <tr>        <td width="59%" height="36" align="left"><strong>用户账号</strong>          <input type="text" name="b2cUser.account" size="20" /> </td>      </tr>       <tr>       <td height="36" align="left"><strong>用户密码 </strong>         <input type="password" name="b2cUser.passwd"/>        </td>      </tr>      <tr>     	<td height="36" align="left"><strong>安全验证</strong>          <input type="text" name="validateCode" id="validateCode"/><jsp:include page="/jsp/common/image.jsp"/></td>     </tr>

?

?

?B2cUser.java类代码如下

public class B2cUser{	private String account;	private String passwd;	public String getAccount() {		return account;	}	public void setAccount(String account) {		this.account = account;	}	public String getPasswd() {		return passwd;	}	public void setPasswd(String passwd) {		this.passwd = passwd;	}}

?

??PersonalAction类,实现了登陆的全部代码,省略getter与setter方法

?

public class PersonalAction extends BaseAction{	private PersonalService personalService;	private B2cUser b2cUser;	/**	 * 验证码	 */	private String validateCode;	/**	 * 登录	 * 	 * @return	 */	public String login()	{		// 如果验证码错误		if (getSession().get("validateCode")==null||!validateCode.equals((String) (getSession().get("validateCode"))))		{			message = "验证码错误";			return "toLogin";			}		//查询用户		b2cUser = personalService.login(b2cUser.getAccount(), b2cUser.getPasswd());		if (b2cUser == null)		{			message = "登录失败,请检查用户名和密码!";			return "toLogin";		}		else			getSession().put(Const.SESSION_USER, b2cUser);		return "toUserCenter";	}}

??下面开始写测试类,首先要加载hibernate与spring的配置文件,代码如下

public class BaseActionTest extends TestCase{	private ApplicationContext context = null;	protected void setUp() throws Exception	{		super.setUp();		context = new FileSystemXmlApplicationContext(new String[]		{ "web/WEB-INF/applicationContext.xml","web/WEB-INF/application-personal.xml" });	}	public ApplicationContext getApplicationContext()	{		return context;	}}

?

通过BaseActionTest来读取配置文件,加载dao,service,这里要注意路径,由于配置文件并非放在classes下面,所以我是使用的FileSystemXmlApplicationContext来加载配置文件。

?

再加载完配置文件之后,下面就开始编写测试类,测试类要实现以下功能:

? 1、模拟用户输入

? 2、模拟程序的验证码

? 3、调用业务层比较登陆的对象是否符合预期值

?

下面我们看如何实现:

1、模拟用户输入

?????? 由于页面中采用的是

?

  <input type="text" name="b2cUser.account" size="20" />

?这种方式来获取表单数据,而PersonalAction中有对应的b2cUser属性?

?

  private B2cUser b2cUser;

?

所以模拟表单输入,可以构建一个B2cUser对象,将其赋值给PersonalAction的b2cUser即可

?

//构建用户的登陆信息B2cUser user=new B2cUser();user.setAccount("13871612726");user.setPasswd("000000");personalAction.setB2cUser(user);

?

2、模拟验证码

?

在页面中,验证码是由validateCode来接收

?

 <input type="text" name="validateCode" id="validateCode"/>

?

它对应personalAction的validateCode属性

?

private String validateCode;

?

所以可以通过下面代码来模拟输入验证码

?

//模拟输入验证码personalAction.setValidateCode("1212");

?

关键点:程序生成的验证码是放入session中的,那么模拟session中已经生成了验证码??

?

//模拟session中生成验证码context=ActionContext.getContext();Map session=new HashMap();session.put("validateCode", "1212");context.setSession(session);

?

前面已经说过了Action中保存的session,request的值其实就是个Map. 所以这里使用了讨巧的办法

?

3、调用业务层比较登陆的对象是否符合预期值

业务层要调用dao,由于业务层与dao层受spring管理,所以之需要从spring配置文件中去读取service即可,但是要记住一点,在PersonalAction中注入了 personalService这个业务层对象

?

private PersonalService personalService;

?

所以,你要保证你测试的action中必须也要人工的"注入"这个对象

?

personalAction.setPersonalService(personalService);

?

OK,下面就一起来看下完整的测试类是怎么编写的

?

public class PersonActionTest extends BaseActionTest{	private ApplicationContext ctx = null;	private PersonalService personalService;	private PersonalAction personalAction;	private ActionContext context;	protected void setUp() throws Exception	{		super.setUp();		ctx = getApplicationContext();		//从配置文件中获取业务层		personalService=(PersonalService) ctx.getBean("personalService");				//action直接用new的方式构造		personalAction=new PersonalAction();		context=ActionContext.getContext();	}		public void testLogin()	{		//模拟用户的登陆信息		B2cUser user=new B2cUser();		user.setAccount("leon");		user.setPasswd("000000");		personalAction.setB2cUser(user);				//模拟输入验证码		personalAction.setValidateCode("1212");		//向session中输入验证码		Map session=new HashMap();		session.put("validateCode", "1212");		context.setSession(session);				personalAction.setPersonalService(personalService);				//运行action		String result=personalAction.login();		B2cUser u=(B2cUser) context.getSession().get(Const.SESSION_USER);				//看看结果是否符合预期		assertEquals("leon", u.getAccount());		assertEquals("toUserCenter", result);	}}

这里需要注意的是,action是使用new的方式构造,而service是从spring的配置文件中读取,然后人工的“注入”到action中。其实在spring的配置文件中已经将action纳入了spring的管理范围之内,这里为什么不直接从spring的配置文件里面读取action而使用new的方式,这里我只能解释:我先开始是从spring里面读取action,但是由于种种我无法解释原因(具体原因您可以亲自试试),我没有这么做。

?

关于如何测试action,我就将自己的心得写这么多,我也不知道自己能坚持这样开发多久,也许项目赶得紧到后来就会放弃这种"费时"的做法,但是现在我还是自己坚持。。也希望大家看了这篇文章能够给与更多的意见,以及更好改进的方式。。。

?

1 楼 黑暗浪子 2009-10-24  
import junit.framework.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.test.AbstractDependencyInjectionSpringContextTests;public class BasicTest extends AbstractDependencyInjectionSpringContextTests		implements Test {	protected String[] getConfigLocations() {		return new String[] { "classpath*:/context/applicationContext-*.xml" };	}	public ApplicationContext getContext(String[] filePath) {		return new ClassPathXmlApplicationContext(filePath);	}}

import java.util.HashMap;import java.util.Map;import org.junit.Test;import org.springframework.context.ApplicationContext;import com.opensymphony.xwork2.ActionContext;import com.ph.irp.BasicTest;public class TestLoginAction extends BasicTest {	private ApplicationContext ap;	private LoginAction action;	private ActionContext context;	@Test	public void testLoginAction() throws Exception {		ap = getContext(getConfigLocations());		action = (LoginAction) ap.getBean("loginAction");		context = ActionContext.getContext();		action.setUsername("XXX");		action.setPassword("YYY");		Map session = new HashMap();		context.setSession(session);				assertEquals("success", action.execute());		assertEquals("XXX", action.getUsername());		assertEquals("XXX", context.getSession().get("userName"));	}}

我的单元测试能通过,我有几点和你不同
1.我的action是getBean方法得到的。
2.我的spring配置文件是放在calsspath里。
3.我除了继承Junit4的API外,我还继承了spring-mock里的AbstractDependencyInjectionSpringContextTests
虽然测试通过了,但我觉得有badsmell,我想请教你看看我哪里有问题。谢谢。
2 楼 CoderDream 2010-01-30  
非常不错,感谢分享!
3 楼 CoderDream 2010-02-04  
personalAction.setPersonalService(personalService); 

这句话很重要,否则会报空指针异常!
  相关解决方案