struts 单元测试(1)

目前,测试驱动开发正变得越来越流行,由于“存在的就是合理的”,这种开发方式必然有其优越之处。作为一个小小程序员,对新鲜技术的追求是工作的重要动力,相信大家都有同感吧。

测试驱动开发是极限编程(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)配置如下所示:

<action
    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类型对象的相同属性之间进行复制:

public ActionForward execute(
    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的内容:

package edu.pku.cc.democenter.test;

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

我的感觉,遇到问题最好先找文档,养成习惯后不但解决问题快了,同时在看文档的同时还可以对其加深理解,何乐而不为呢。

posted @ 2010-04-20 09:38  明之道  阅读(1211)  评论(0编辑  收藏  举报