虽然 Struts 正在慢慢退出 Web 框架的历史舞台,但它的遗产仍然存在,存在的形式主要是需要测试和维护的应用程序。这个月,Andrew Glover 向您介绍如何使用 JUnit 的 StrutsTestCase、DbUnit 以及在这个系列中迄今为止学到的一些工具,把以质量为中心的方法用于 Struts 上的测试(可以这么说)。
基于 Java™ 的 Web 开发领域最近出现了丰富的竞争性技术。启动新项目的开发人员可以在许多不同的框架之间进行选择,包括 JavaServer Faces、Tapestry、Shale、Grails 和 Seam (只列举众多机灵的名称中的几个)。很快,我们就可以通过 JRuby 框架在 Java 编程中使用 Ruby on Rails 了!
但就在不远的过去,只有一个 Java Web 开发框架卓然而立。Struts 是第一个在 Java 世界掀起风暴的框架,而且多年以来,好像是如果一个项目不用 Struts 构建就没有前途一样。没有 Struts 经验的 Java 开发人员很稀少,也很不幸,就像今天的开发人员没有听说过 Ruby on Rails 一样。
|
即使 Struts 正慢慢地从舞台中央退去(原来的基本框架,现在叫做 Struts 1,似乎正在退出 Web 框架的历史舞台),但它的遗产仍然存在,既以 Shale (请参阅 参考资料)的形式存在,又以运行在世界各地的成千上万的遗留应用程序的形式存在。因为许多企业宁愿测试和维护这些应用程序而不愿意花钱重新编写它们,所以理解 Struts 应用程序的一些缺陷,以及如何围绕它们进行重构,是个好主意。
这个月,我要把以质量为核心的方法用于 Struts 应用程序的测试场景。结合现实,这个场景围绕着最普遍的 Struts 构造:深受喜爱的 Action
类。
Struts 的革新之一就是把 Web 开发从 Servlet 移进了 Action
类。这些类包含业务逻辑,以 JavaBean 的形式(通常叫做 ActionForm
)把数据传送到 JSP。然后 JSP 处理应用程序视图。Struts 到 MVC 的方法非常容易掌握,以至于许多开发团队冒失地闯进去,而很少考虑与 Action
相关的长期设计和维护问题。
|
虽然在那个时候(过去的自由时光啊)可能没人想过,但 Struts Action
类通常成为复杂性的保护所。像在老的 EJB 架构中声名狼籍的会话 Facade 一样,Action
类会成为特定业务过程的严格伪装,或者通过直接调用 EJB,通过打开数据库连接,或者通过调用其他高度依赖的对象。Action
类还有输出耦合(通过 java.servlet
API 包中的对象,例如 HttpServletRequest
和 HttpServletResponse
),从而极难把它们隔离出来测试。
隔离出来测试 Action
类的困难意味着它们可以很容易变得相当复杂 —— 特别是当它们变成越来越深入地与遗留框架耦合的时候。现在我们来看这个困难在真实的遗留应用程序场景中作用的情况。
即使最简单的 Struts Action
类也会是个测试挑战。例如,以清单 1 中的 execute()
方法为例;它看起来足够简单,可以测试,但是真的么?
public ActionForward execute(ActionMapping mapping, ActionForm aForm, HttpServletRequest req, HttpServletResponse res) throws Exception { try{ String newPassword = ((ChangePasswordForm)aForm).getNewPassword1(); String username = ((ChangePasswordForm)aForm).getUsername(); IUser user = DataAclearcase/" target="_blank" >ccessUtils.getDaos().getUserDao().findUserByUsername(username); user.digestAndSetPassword(newPassword); DataAccessUtils.getDaos().getUserDao().saveUser(user); }catch(Throwable thr){ return findFailure(mapping, aForm, req, res); } return findSuccess(mapping, aForm, req, res); } |
但是,就像在图 1 中可以看到的,在试图隔离 ChangePasswordAction
类并检验 execute()
方法时,该类给出了一些有代表性的挑战。为了有效地测试 execute()
方法,必须处理三层耦合。首先,到 Struts 自身的耦合;其次,Servlet API 代表一个障碍;最后,到业务对象包的耦合,进一步检查业务对象包,还会有数据访问层使用 Hibernate 和 Spring。
|
对于更高的复杂性,请注意 清单 1 中的代码如何把 aForm
参数转换成 ChangePasswordForm
对象,它是 Struts ActionForm
类型。这些 JavaBeans 有一个 validate
方法,这个方法由 Struts 在调用 Action
类的 execute()
方法之前调用。
在清单 2 中,可以看到所有这个复杂性会在哪里发生。ChangePasswordForm
的 validate()
方法的代码片段演示了保证两个属性(newPassword1
和 newPassword2
)不为空并彼此相等的简单逻辑。但是,如果 Struts 发现 errors
集合(类型为 ActionErrors
)包含一些 ActionError
对象,就会沿着错误路径走,例如带着出错消息重新显示 Web 页面。
if((newPassword1 == null) || (newPassword1.length() < 1)) { errors.add("newPassword1", new ActionError("error.changePassword.newPassword1Required"));}if((newPassword2 == null) || (newPassword2.length() < 1)) { errors.add("newPassword2", new ActionError("error.changePassword.newPassword2Required"));}if((newPassword1 != null) && (newPassword2 != null)) { if(!newPassword1.equals(newPassword2)) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.changePassword.passwordsDontMatch")); }} |
清单 1 和 清单 2 的代码不特殊也不特定于某个领域。它是无数应用程序中都包含的简单口令修改逻辑。如果正在测试 Struts 遗留应用程序,将不得不花些时间处理口令逻辑,但是如何用可重复的方式测试它呢?