目前,测试驱动开发正变得越来越流行,由于“存在的就是合理的”,这种开发方式必然有其优越之处。作为一个小小程序员,对新鲜技术的追求是工作的重要动力,相信大家都有同感吧。
测试驱动开发是极限编程(XP)的重要组成部分,从字面上就可以看出,它是先有测试再有代码的。这听起来似乎有点奇怪,实际上,可以把测试用例当作需求,程序员的工作就是写出满足这种需求的代码,即让这些测试都能够通过。在刚刚写好测试用例的时候,由于还没有实际代码,因此这时运行测试的结果一定不会通过,随着代码的增加,越来越多的测试得以通过,最后全部通过。这时,基本上可以说系统的每个功能单元都是正确的,剩下的工作就是集成测试了。而这些测试用例的使命并未结束,因为代码会不断修改,我们需要经常(例如每天)运行测试来检验目前的代码是否能够通过测试。很明显,这种检验是完全自动化的,既快速有保证质量。
在使用Struts构件的系统里,的每一个Action都可以看作一个功能单元,它们构成了系统的主体。(当然,你的业务逻辑并不一定都直接写在Action的execute方法中,但在该方法中会以一定方式调用这些逻辑。)StrutsTestCase(http://strutstestcase.sourceforge.net/)是JUnit的一个包装,它提供了非常方便的测试这些Action的方法。它提供两种测试方式:Mock(模拟对象)和Cactus(真实环境),目前我只试验了前者,发现效果很好。
由于我已经写了一些代码,因此我进行的不能算是真正的测试“驱动”开发,我主要是把StrutsTestCase作为一种自动化的测试工具来用,起到保证代码质量的作用。
举例来说,我有一个类名为SaveTeacherAction的Action,其所在模块名为teacher,访问路径为/save,与他相关联的是名为TeacherForm的ActionForm,(struts-config-teacher.xml)配置如下所示:
attribute="teacherForm"
input="/form/teacher.jsp"
name="teacherForm"
path="/save"
scope="request"
type="edu.pku.cc.democenter.teacher.action.SaveTeacherAction">
<forward name="success" path="/list.do" redirect="true" />
</action>
SaveTeacherAction类中的execute方法如下,其中HibernateDAO是我自己写的用来进行持久化操作的包装类,BeauUtils是Jakarta commons包中的一个实用工具,可以在两个Bean类型对象的相同属性之间进行复制:
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
HibernateDAO dao=HibernateDAO.getInstance(getServlet().getServletContext());
TeacherForm teacherForm = (TeacherForm) form;
Teacher t=null;
if("Create".equals(teacherForm.getAction())){
t=new Teacher();
}
if("Edit".equals(teacherForm.getAction())){
t=(Teacher)dao.findByCode(Teacher.class,teacherForm.getCode());
}
BeanUtils.copyProperties(t,teacherForm);
dao.saveOrUpdate(t);
return mapping.findForward("success");
}
下面,我要写一个测试用例来对这个Action进行测试。在Eclipse里使用StrutsTestCase非常简单,只需要在工程的classpath里包含strutstest-2.1.2.jar这个包以及junit的包就可以开始编写了。我为这个类起名为TestSaveTeacherAction,即Action类名前加上Test字样,所在包也与实际代码分开,使用edu.pku.cc.democenter.test的名称(与之对比,SaveTeacherAction的包名为edu.pku.cc.democenter.teacher.action,democenter是我们这个项目的名称)。
现在来看一下TestSaveTeacherAction的内容:
import servletunit.struts.MockStrutsTestCase;
import edu.pku.cc.democenter.teacher.form.TeacherForm;
public class TestTeacherAction extends MockStrutsTestCase{
protected void setUp() throws Exception {
super.setUp();
setConfigFile("teacher","/WEB-INF/struts-config-teacher.xml");
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testSaveTeacherAction_Create(){
setRequestPathInfo("/teacher","/save");
TeacherForm form=makeForm();
form.setAction("Create");
form.setCode("test.create");
setActionForm(form);
actionPerform();
verifyForward("success");
}
public void testSaveTeacherAction_Edit(){
setRequestPathInfo("/teacher","/save");
TeacherForm form=makeForm();
form.setAction("Create");
form.setCode("test.edit");
setActionForm(form);
actionPerform();
form.setAction("Edit");
form.setBirthDate("1979-1-10");
setActionForm(form);
actionPerform();
verifyForward("success");
}
private TeacherForm makeForm(){
TeacherForm form=new TeacherForm();
form.setAction("Create");
form.setBirthDate("1979-1-1");
form.setCode("test.001");
form.setEmail("test@test.com");
form.setName("test");
form.setShortName("CS");
form.setTel("62760000");
return form;
}
}
使用Mock方式的测试用例都继承servletunit.struts.MockStrutsTestCase这个类,setUp和tearDown方法分别是测试前后进行准备和善后工作的地方。要运行的测试方法都以test开头,系统会自动调用这些方法。
由于SaveTeacherAction根据request中的action参数有两种运行方式:Create和Edit,所以我写了两个test方法对它们分别进行测试。这里要注意的是,这些test方法运行的顺序是不确定的,不要认为可以先运行create的测试,再以此为基础对刚刚create的对象进行edit,看testSaveTeacherAction_Edit方法的写法。
我在setUp方法里指定了模块和对应的配置文件名称,如果没有模块可以省去这一步。在实际的testSaveTeacherAction_Create方法里,首先用setRequestPathInfo方法指定要测试的Action的路径和模块,如果没有模块可以用一个参数的同名方法。然后构造一个ActionForm,对于我的例子就是TeacherForm,为了简化代码,我写了一个makeForm方法来生成一个填好值的TeacherForm。用setActionForm将这个TeacherForm连接到Action,actionPerform方法通知执行Action操作。这时按照我们的设想,Action会将请求转发到一个名为success的Forward,所以我们使用verifyForward("success")方法验证是否进行了转发。在这一步如果失败,就表明此单元测试失败,否则为成功。
testSaveTeacherAction_Edit方法与其类似,只是要注意在这个方法里要自己建立对象再修改,而不能使用testSaveTeacherAction_Create方法里建立的对象,因为这两个方法的执行顺序是不确定的。
要在Eclipse里运行这个测试也很简单,先双击打开这个类,然后在Run菜单里选择Run As->JUnit Test就可以了,你会看到一个JUnit视图,里面有一个绿色的进度条,如果走到头还保持绿色表示所有的测试都成功,否则会变成红色,并在下面显示异常的堆栈信息。(成就感哦)
好了,今天对StrutsTestCase作了一个很简单的介绍,我也是刚刚开始使用它,今后肯定还会遇到问题的,敬请关注后续报道。
另,相关的一些文章可以在网上找到,作为入门很好,例如:
http://plateau.sicool.com/article/tdd/strutstestcast_junit_tdd.htm
我的感觉,遇到问题最好先找文档,养成习惯后不但解决问题快了,同时在看文档的同时还可以对其加深理解,何乐而不为呢。