lustdevil 2009-07-18
项目中没有测试用例给人最头疼的就是不好重构,甚至不敢重构,我现在所参与的一个B2B项目从开始到现在压根没有任何一个测试用例,甚至都没有重构过,看到有人一个方法2000行,心里都在发颤:如果那家伙离职了,他的代码的维护就是件非常头痛的事情,与其维护,不如重写算了。。然而B2B项目经常需要适应用户去做相应的改动,所以没到这个时候,就是我最害怕的时候,改完了,发布上线,就要经历那心惊肉跳的稳定期。现在维护一个B2B项目的同时,又要开始另外B2C项目的开发,不想重蹈覆辙,这次干脆在项目一开始时候就搭建测试框架。这次主要介绍,如何在struts2,spring,hibernate整合的时候,使用junit来测试struts2的action。
首先,本示例用到的类如下:BaseAction,PersonalAction,PersonalService,
B2cUIser,配置文件省略,使用eclipse自带的JUnit3.
下面,让我们看一个测试场景:用户登陆,需要输入用户名,密码和验证码,在用户登陆成功之后,将用户对象保存在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,我就将自己的心得写这么多,我也不知道自己能坚持这样开发多久,也许项目赶得紧到后来就会放弃这种"费时"的做法,但是现在我还是自己坚持。。也希望大家看了这篇文章能够给与更多的意见,以及更好改进的方式。。。