使用StrutsTestCase對Action進行單元測試簡介

 

目前,測試驅動開發正變得越來越流行,由於“存在的就是合理的”,這種開發方式必然有其優越之處。作爲一個小小程序員,對新鮮技術的追求是工作的重要動力,相信大家都有同感吧。

測試驅動開發是極限編程(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)配置如下所示:

None.gif<action
None.gif    attribute
="teacherForm"
None.gif    input
="/form/teacher.jsp"
None.gif    name
="teacherForm"
None.gif    path
="/save"
None.gif    scope
="request"
None.gif    type
="edu.pku.cc.democenter.teacher.action.SaveTeacherAction">
None.gif    
<forward name="success" path="/list.do" redirect="true" />
None.gif
</action>
None.gif

SaveTeacherAction類中的execute方法如下,其中HibernateDAO是我自己寫的用來進行持久化操作的包裝類,BeauUtils是Jakarta commons包中的一個實用工具,可以在兩個Bean類型對象的相同屬性之間進行復制:

None.gifpublic ActionForward execute(
None.gif    ActionMapping mapping,
None.gif    ActionForm form,
None.gif    HttpServletRequest request,
None.gif    HttpServletResponse response)
None.gif    throws Exception {
None.gif
None.gif    HibernateDAO dao
=HibernateDAO.getInstance(getServlet().getServletContext());
None.gif    TeacherForm teacherForm 
= (TeacherForm) form;
None.gif    Teacher t
=null;
None.gif    
None.gif    
if("Create".equals(teacherForm.getAction())){
None.gif        t
=new Teacher();
None.gif    }
None.gif    
if("Edit".equals(teacherForm.getAction())){
None.gif        t
=(Teacher)dao.findByCode(Teacher.class,teacherForm.getCode());
None.gif    }
None.gif    
None.gif    BeanUtils.copyProperties(t,teacherForm);
None.gif    
None.gif    dao.saveOrUpdate(t);
None.gif    
return mapping.findForward("success");
None.gif}
None.gif

下面,我要寫一個測試用例來對這個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的內容:

None.gifpackage edu.pku.cc.democenter.test;
None.gif
None.gifimport servletunit.struts.MockStrutsTestCase;
None.gifimport edu.pku.cc.democenter.teacher.form.TeacherForm;
None.gif
None.gifpublic class TestTeacherAction extends MockStrutsTestCase{
None.gif
None.gif    protected 
void setUp() throws Exception {
None.gif        super.setUp();
None.gif        setConfigFile(
"teacher","/WEB-INF/struts-config-teacher.xml");
None.gif    }
None.gif
None.gif    protected 
void tearDown() throws Exception {
None.gif        super.tearDown();
None.gif    }
None.gif
None.gif    public 
void testSaveTeacherAction_Create(){
None.gif        setRequestPathInfo(
"/teacher","/save");
None.gif        TeacherForm form
=makeForm();
None.gif        form.setAction(
"Create");
None.gif        form.setCode(
"test.create");
None.gif        setActionForm(form);
None.gif        actionPerform();
None.gif        verifyForward(
"success");
None.gif    }
None.gif    
None.gif    public 
void testSaveTeacherAction_Edit(){
None.gif        setRequestPathInfo(
"/teacher","/save");
None.gif        TeacherForm form
=makeForm();
None.gif        form.setAction(
"Create");
None.gif        form.setCode(
"test.edit");
None.gif        setActionForm(form);
None.gif        actionPerform();
None.gif        
None.gif        form.setAction(
"Edit");
None.gif        form.setBirthDate(
"1979-1-10");
None.gif        setActionForm(form);
None.gif        actionPerform();
None.gif        verifyForward(
"success");
None.gif    }
None.gif
None.gif    private TeacherForm makeForm(){
None.gif        TeacherForm form
=new TeacherForm();
None.gif        form.setAction(
"Create");
None.gif        form.setBirthDate(
"1979-1-1");
None.gif        form.setCode(
"test.001");
None.gif        form.setEmail(
"[email protected]");
None.gif        form.setName(
"test");
None.gif        form.setShortName(
"CS");
None.gif        form.setTel(
"62760000");
None.gif        
return form;
None.gif    }
None.gif}
None.gif

使用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視圖,裏面有一個綠色的進度條,如果走到頭還保持綠色表示所有的測試都成功,否則會變成紅色,並在下面顯示異常的堆棧信息。(成就感哦)

junit.gif

好了,今天對StrutsTestCase作了一個很簡單的介紹,我也是剛剛開始使用它,今後肯定還會遇到問題的,敬請關注後續報道。

另,相關的一些文章可以在網上找到,作爲入門很好,例如:

http://plateau.sicool.com/article/tdd/strutstestcast_junit_tdd.htm

我的感覺,遇到問題最好先找文檔,養成習慣後不但解決問題快了,同時在看文檔的同時還可以對其加深理解,何樂而不爲呢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章