<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-55  評(píng)論-208  文章-0  trackbacks-0
    不好意思,最近由于在趕項(xiàng)目所以這篇文章今天才有時(shí)間寫出來(lái)

    首先講講taglib的使用目的,只有明確的使用目的我們才能寫出明確的單元測(cè)試
    通常我們自定義的taglib都是為了根據(jù)一些參數(shù)達(dá)到我們需要view層樣式,在我的項(xiàng)目中一般比較少的使用自定義標(biāo)簽的body形式(body一般是為了通過(guò)標(biāo)簽達(dá)到框架級(jí)的頁(yè)面結(jié)構(gòu)),因此,對(duì)于一個(gè)taglib來(lái)說(shuō)它一般要做事情有:
    1、獲取參數(shù)
    2、根據(jù)參數(shù)獲取結(jié)果集(通常這個(gè)主要是bl層的任務(wù))
    3、根據(jù)結(jié)果集得到輸出樣式(得到的樣式一般都是一個(gè)html或者wml的字符串)
    4、把得到的輸出樣式最終輸出到頁(yè)面上

    根據(jù)上面的分析其實(shí)我們可以看出我們需要把測(cè)試的焦點(diǎn)集中在3上,因?yàn)槠渌娜蝿?wù)主要是通過(guò)調(diào)用其它封裝好的方法來(lái)實(shí)現(xiàn)的。
    用一個(gè)實(shí)例來(lái)介紹一下我的做法吧:ShowCatalogTag,主要是根據(jù)傳遞的類別id,顯示相關(guān)的類別信息和子類信息。
    根據(jù)需求我們不難看出這個(gè)標(biāo)簽的主要功能是
    1、獲取類別ID和相關(guān)樣式顯示參數(shù)
    2、根據(jù)類別id獲取類別相關(guān)信息和子類別信息
    3、根據(jù)類別信息結(jié)果集和顯示參數(shù)得到輸出wml代碼
    4、把類別樣式最終輸出到頁(yè)面上
    根據(jù)需求分析我們可以設(shè)計(jì)出我們標(biāo)簽的主要方法有:
    getOutWml()的到最終的wml輸出
    getCatalogLayout(List catalogList)根據(jù)類別結(jié)果集得到類別樣式

    然后,根據(jù)上述設(shè)計(jì),我們可以首先寫我們的單元測(cè)試了
    單元測(cè)試一般是從最底層的實(shí)現(xiàn)方法開(kāi)始寫起,所以我們首先寫testGetCatalogLayout
     1public void testGetCatalogLayout() throws Exception {
     4        List catalogList=new ArrayList();
     5        CsCatalog testcatalog=new CsCatalog();
     6        testcatalog.setCatalogName("ring");
     7        testcatalog.setId(23l);
     8        catalogList.add(testcatalog);
     9         //得到待測(cè)方法結(jié)果
    12        StringBuffer result = sct.getCatalogLayout(catalogList);
    13        logger.debug(result);
    14        //設(shè)置期望結(jié)果
    15        StringBuffer outPut = new StringBuffer();
    16        if (null != catalogList && catalogList.size() != 0{
    17            CsCatalog catalog = (CsCatalog) catalogList.get(0);
    18            Map parameterMap = new LinkedMap();            
    19            for (int i = 1; i < catalogList.size() - 1; i++{
    20
    21                catalog = (CsCatalog) catalogList.get(i);
    22                parameterMap = new LinkedMap();
    23                parameterMap.put("catalogid", Long.toString(catalog.getId()));
    24                outPut.append(catalog.getCatalogName());                
    25            }
        
    26        }

    27        //進(jìn)行斷言判斷期望和實(shí)際結(jié)果
    28        assertEquals(outPut.toString(),result.toString());
    29    }

    此時(shí),有關(guān)getCatalogLayout的測(cè)試方法已經(jīng)寫完了,但是實(shí)際上這個(gè)方法我們還沒(méi)有寫呢。所以在eclipse中會(huì)顯示錯(cuò)誤我們使用eclipse的自動(dòng)完成功能來(lái)在標(biāo)簽中實(shí)現(xiàn)一個(gè)空getCatalogLayout方法,下面我將寫getCatalogLayout方法的實(shí)現(xiàn) : 

    public StringBuffer getCatalogLayout(List catalogList) {
            StringBuffer outPut 
    = new StringBuffer();
            
    if (null != catalogList && catalogList.size() != 0{
                CsCatalog catalog 
    = (CsCatalog) catalogList.get(0);
                Map parameterMap 
    = new LinkedMap();            
                
    for (int i = 1; i < catalogList.size() - 1; i++{

                    catalog 
    = (CsCatalog) catalogList.get(i);
                    parameterMap 
    = new LinkedMap();
                    parameterMap.put(
    "catalogid", Long.toString(catalog.getId()));
                    outPut.append(catalog.getCatalogName());                
                }

            }
            
            
    return outPut;
        }

    然后運(yùn)行eclipse的run->junit test,ok,我們期待的綠條來(lái)了,如果要是紅條,那么你就需要仔細(xì)檢查一下你的方法實(shí)現(xiàn)代碼了:)

    上面的方法其實(shí)主要是說(shuō)了一下如何用tdd的方式來(lái)思考和編寫代碼,其實(shí)getCatalogLayout這個(gè)方法基本上是和標(biāo)簽環(huán)境隔離的,需要傳遞的參數(shù)只有已知的cataloglist
    下面將介紹一下在標(biāo)簽中使用到相關(guān)環(huán)境時(shí)的解決方案
    在標(biāo)簽中我們一般需要使用的外部環(huán)境參數(shù)主要就是pageContext,而在pageContext中有我們需要的request,webapplicationcontext,response等等環(huán)境變量。
    因此要進(jìn)行完整的標(biāo)簽單元測(cè)試就必須要考慮到把加入相關(guān)的環(huán)境變量
    首先考慮加載spring的WebApplicationContext,前一篇文章講DAO單元測(cè)試的時(shí)候我提到過(guò)用springmock來(lái)加載spring的上下文,但是springmock加載的是ClassPathApplicationContext,兩者是不一樣的,而我查找了資料后沒(méi)有找到相關(guān)的轉(zhuǎn)換方法,結(jié)果我只有通過(guò)配置文件的路徑得到WebApplicationContext,下面是一個(gè)工具類用于對(duì)spring的相關(guān)信息進(jìn)行加載

    /**
     * $Id:$
     *
     * Copyright 2005 easou, Inc. All Rights Reserved.
     
    */

    package test.spring.common;

    import javax.servlet.ServletContext;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;

    import org.springframework.mock.web.MockPageContext;
    import org.springframework.mock.web.MockServletContext;
    import org.springframework.web.context.ContextLoader;
    import org.springframework.web.context.ContextLoaderListener;
    import org.springframework.web.context.WebApplicationContext;

    import test.PathConfig;

    import com.easou.commons.web.taglib.BaseTag;

    public class SpringTestUtil {

        
    /**
         * 
    @author rocket 初始化tag所需的MockServletContext
         * 
         
    */

        
    protected static MockServletContext getSpringMockSC() {
            MockServletContext mockServletContext 
    = new MockServletContext();
            mockServletContext.addInitParameter(
                    ContextLoader.CONFIG_LOCATION_PARAM, PathConfig
                            .getStringXmlPath(PathConfig.springxml));
            ServletContextListener listener 
    = new ContextLoaderListener();
            ServletContextEvent event 
    = new ServletContextEvent(mockServletContext);
            listener.contextInitialized(event);
            
    return mockServletContext;

        }


        
    /**
         * 
    @author rocket 針對(duì)tag設(shè)置Spring的上下文
         * 
    @param tag
         
    */

        
    public static void setSpringContextInTag(BaseTag tag) {

            MockPageContext mockPC 
    = new MockPageContext(getSpringMockSC());
            tag.setPageContext(mockPC);
        }


        
    /**
         * 
    @author rocket 獲得spring的上下文
         * 
    @return spring的上下文
         
    */

        
    public static WebApplicationContext getSpringContext() {
            MockServletContext mockServletContext 
    = getSpringMockSC();
            
    return (WebApplicationContext) mockServletContext
                    .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        }


        
    /**
         * 
    @author rocket 對(duì)servletContext設(shè)置spring的上下文
         * 
    @param servletContext
         
    */

        
    public static void setSpringContext(ServletContext servletContext) {

            servletContext.setAttribute(
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
                    getSpringContext());
        }


    }

    這個(gè)類中的幾個(gè)方法可以對(duì)mock的pagecontext或者servletcontext加載spring的上下文

    這里簡(jiǎn)單介紹一下我使用的環(huán)境模擬工具:
    我沒(méi)有使用流行的mockObject和MockRunner,主要是因?yàn)槲业捻?xiàng)目基本框架是spring+Struts,而對(duì)這兩個(gè)框架的模擬有更有針對(duì)性地springmock和strutstestcase,這樣在我需要加載struts相關(guān)配置信息時(shí),strutstestcase提供了更好的支持。
    比如我這里需要使用到strust中配置好的properties信息,那么下面各測(cè)試基類就基本實(shí)現(xiàn)的我的要求

    /**
     * $Id:$
     *
     * Copyright 2005 easou, Inc. All Rights Reserved.
     
    */

    package test.struts.common;

    import org.springframework.mock.web.MockPageContext;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    import org.springframework.web.context.WebApplicationContext;

    import servletunit.struts.MockStrutsTestCase;
    import test.spring.common.SpringTestUtil;

    import com.easou.commons.web.taglib.BaseTag;

    public class BaseStrutsTestCase extends MockStrutsTestCase {

        
    /** The transaction manager to use */
        
    protected PlatformTransactionManager transactionManager;

        
    /**
         * TransactionStatus for this test. Typical subclasses won't need to use it.
         
    */

        
    protected TransactionStatus transactionStatus;

        
    /**
         * 
    @author rocket 設(shè)施struts配置文件的路徑
         * 
         
    */

        
    protected void setUp() throws Exception {
            
    super.setUp();
            setConfigFile(
    "/WEB-INF/struts-config.xml");
        }


        
    protected void tearDown() throws Exception {
            
    super.tearDown();
            WebApplicationContext wac 
    = (WebApplicationContext) context
                    .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            transactionManager 
    = (PlatformTransactionManager) wac
                    .getBean(
    "transactionManager");
            transactionStatus 
    = transactionManager
                    .getTransaction(
    new DefaultTransactionDefinition());
            transactionManager.rollback(
    this.transactionStatus);
            logger.info(
    "Rolled back transaction after test execution");
        }


        
    /**
         * 
    @author rocket 根據(jù)默認(rèn)路徑初始化Struts配置
         * 
         
    */

        
    protected void strutsInit() {

            strutsInit(
    "/index.do");
        }


        
    /**
         * 
    @author rocket 根據(jù)路徑初始化Struts配置
         * 
    @param path
         
    */

        
    protected void strutsInit(String path) {

            setRequestPathInfo(path);
            actionPerform();
            SpringTestUtil.setSpringContext(context);
        }


        
    /**
         * 
    @author rocket 對(duì)于tag設(shè)施struts的配置 同時(shí)加載spring的配置
         * 
    @param tag
         
    */

        
    protected void setStrutsContextInTag(BaseTag tag) {
            strutsInit();

            MockPageContext mockPC 
    = new MockPageContext(context,request,response);
            tag.setPageContext(mockPC);
        }


        
    /**
         * 
    @author rocket 測(cè)試struts加載是否成功
         * 
         
    */

        
    public void testStrutsInit() {
            strutsInit();
            verifyNoActionErrors();
            verifyForward(
    "success");
            assertTrue(
    "struts has bean init", isInitialized);
            assertNotNull(
    "ServletContext is not null", context);
            assertNotNull(
    "request is not null", request);
        }


    }


    下面回到我們的ShowCatalogTag中來(lái),我要開(kāi)始對(duì)getOutWml進(jìn)行測(cè)試了,在測(cè)試這個(gè)方法之前我們需要對(duì)ShowCatalogTag加載模擬的環(huán)境信息:

    public class ShowCatalogsTagTest extends BaseTagTest{
        
        ShowCatalogsTag sct
    =new ShowCatalogsTag();
        
    public void testGetOutWml() throws Exception {
            
            String channel
    ="Ring";
            String parentid 
    = "-1";
            sct.setChannel(channel);
            sct.setHerf(
    "/channel/RingCatalogRcPage.do");
            sct.setParentid(parentid);
            sct.setRowCount(
    "2");
            sct.setSplitchar(
    "$");
            sct.setLongName(
    "false");
            sct.setFirstBracket(
    "false");
            sct.setFirstHerf(
    null);
            
    //這里針對(duì)tag加載相關(guān)的struts和spring環(huán)境
            setStrutsContextInTag(sct);
            
    //得到實(shí)際結(jié)果
            StringBuffer result = sct.getOutWml();
            logger.debug(result);
            
    //獲得期望結(jié)果
            List catalogList = ChannelMGR.getCatalogsByChannelAndParentId(channel,
                    parentid);
            
    //由于getCatalogLayout已經(jīng)測(cè)試過(guò),所以getCatalogLayout方法可以直接調(diào)用
            StringBuffer expect = sct.getCatalogLayout(catalogList);     
                //斷言判斷
               assertEquals(expect ,result );
        }

    為了簡(jiǎn)化測(cè)試環(huán)境設(shè)置代碼,所以我在BaseTagTest中定義好了setStrutsContextInTag方法用于加載spring和struts的相關(guān)信息
     下面針對(duì)getoutWml的測(cè)試我可以很快得到實(shí)現(xiàn)

    public StringBuffer getOutWml() {        
            log.debug(
    "channel" + channel);
            log.debug(
    "parentid" + parentid);
            ChannelMGR 
    = (ChannelManager) getBean("ChannelManager");
            List catalogList 
    = ChannelMGR.getCatalogsByChannelAndParentId(channel,
                    parentid);
            StringBuffer outPut 
    = getCatalogLayout(catalogList);    
            
    return outPut;
        }

    后面的事情就是我們最期待得run->junit test,然后得到一個(gè)漂亮的綠條。當(dāng)你沒(méi)有得到green bar時(shí)首先要檢查的是你的測(cè)試邏輯和測(cè)試環(huán)境是否正確,然后再檢查你的實(shí)現(xiàn)代碼是否正確。

    到此為止,我的ShowCatalogTag就已經(jīng)開(kāi)發(fā)完成了。有人也學(xué)會(huì)問(wèn)怎么沒(méi)有沒(méi)有見(jiàn)到你的doStartTag這個(gè)方法呢,是這樣的我封裝了一個(gè)Basetag定義了公用的方法


        
    protected Object getBean(String beanName) {
            ctx 
    = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(pageContext
                            .getServletContext());
            
    return ctx.getBean(beanName);
        }


        
    protected MessageResources getResources(HttpServletRequest request,
                String key) 
    {
            ServletContext context 
    = pageContext.getServletContext();
            ModuleConfig moduleConfig 
    = ModuleUtils.getInstance().getModuleConfig(
                    request, context);
            
    return (MessageResources) context.getAttribute(key
                    
    + moduleConfig.getPrefix());
        }


        
    public int doStartTag() throws JspException {
            initManager();
            
    try {
                
    this.pageContext.getOut().write(getOutWml().toString());
            }
     catch (IOException ex) {
                log.error(ex);
            }

            
    return this.SKIP_BODY;
        }


    以上的過(guò)程就是我使用TDD的方法來(lái)進(jìn)行一個(gè)taglib的開(kāi)發(fā),總結(jié)來(lái)說(shuō),TDD的好處有:
    1、開(kāi)發(fā)時(shí)結(jié)構(gòu)清晰,各個(gè)方法分工明確
    2、各個(gè)方法目的明確,在實(shí)現(xiàn)之前我已經(jīng)明確了這個(gè)方法的實(shí)現(xiàn)目的
    3、開(kāi)發(fā)快捷,以往開(kāi)發(fā)taglib進(jìn)行調(diào)試需要啟動(dòng)web應(yīng)用,但是在我這個(gè)開(kāi)發(fā)過(guò)程中可以看到我并沒(méi)有啟動(dòng)任何app應(yīng)用
    4、回歸測(cè)試,當(dāng)需求發(fā)生變動(dòng)時(shí),要檢測(cè)變動(dòng)是否會(huì)對(duì)已有功能造成影響,只需要執(zhí)行以前的單元測(cè)試就可以了

    當(dāng)然,tdd的開(kāi)發(fā)方式將會(huì)耗費(fèi)大量的時(shí)間在外部環(huán)境的模擬上,我在模擬spring和struts的環(huán)境時(shí)就花費(fèi)了比較久的時(shí)間在研究。不過(guò),當(dāng)我最后得到高質(zhì)量的實(shí)現(xiàn)代碼時(shí),我感覺(jué)到,這個(gè)代價(jià)是值得的

     


     

    posted on 2007-02-06 17:46 rocket 閱讀(1565) 評(píng)論(2)  編輯  收藏

    評(píng)論:
    # re: taglib單元測(cè)試 2007-02-07 09:10 | sunflower
    路過(guò),過(guò)來(lái)踩一下.
    我加你為blog friend,可以吧.
    學(xué)習(xí)ing....  回復(fù)  更多評(píng)論
      
    # re: taglib單元測(cè)試 2007-02-07 11:25 | steady
    我也來(lái)踩一個(gè)了,收藏一下  回復(fù)  更多評(píng)論
      

    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 99re6免费视频| 黄色成人网站免费无码av| 在线a级毛片免费视频| 免费在线观看中文字幕| 亚洲高清国产AV拍精品青青草原| 亚洲乱码在线视频| 一级人做人a爰免费视频| 永久免费在线观看视频| 亚洲AⅤ无码一区二区三区在线 | 亚洲免费在线视频观看| 免费看又黄又爽又猛的视频软件| 无码国产精品一区二区免费16| 在线精品免费视频| 亚洲AV日韩精品久久久久久| 亚洲AV永久无码精品网站在线观看 | 67pao强力打造67194在线午夜亚洲 | 四虎影视成人永久免费观看视频 | 亚洲av一本岛在线播放| 亚洲免费日韩无码系列| www.999精品视频观看免费| 国产亚洲AV手机在线观看| 欧洲 亚洲 国产图片综合| 成全视频在线观看免费| 国产成人aaa在线视频免费观看| 亚洲邪恶天堂影院在线观看| 在线观看亚洲精品专区| 四虎精品视频在线永久免费观看 | 国产亚洲精品成人a v小说| 亚洲精品国产国语| 无码国产精品一区二区免费vr | 免费一级特黄特色大片在线| 亚洲日韩乱码久久久久久| aaa毛片视频免费观看| 国产高清在线免费| 亚洲免费在线视频观看| 久久精品乱子伦免费| 亚洲五月午夜免费在线视频| 亚洲AV无码专区在线观看成人| 69精品免费视频| 国产亚洲人成网站观看| 又大又硬又粗又黄的视频免费看|