隨筆-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)方法開始寫起,所以我們首先寫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è)方法我們還沒有寫呢。所以在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,兩者是不一樣的,而我查找了資料后沒有找到相關(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)境模擬工具:
          我沒有使用流行的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),我要開始對(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)你沒有得到green bar時(shí)首先要檢查的是你的測(cè)試邏輯和測(cè)試環(huán)境是否正確,然后再檢查你的實(shí)現(xiàn)代碼是否正確。

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

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

           


           

          posted on 2007-02-06 17:46 rocket 閱讀(1567) 評(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)航:
           
          主站蜘蛛池模板: 彩票| 佛教| 商南县| 稻城县| 社旗县| 东辽县| 巴中市| 青海省| 巩留县| 调兵山市| 苗栗县| 托里县| 南华县| 东兴市| 永靖县| 朝阳市| 金昌市| 衡南县| 抚松县| 定日县| 卫辉市| 阿图什市| 芦山县| 肥东县| 永宁县| 灌云县| 安塞县| 远安县| 若羌县| 保康县| 鹤庆县| 静海县| 建宁县| 独山县| 黔东| 石嘴山市| 泰兴市| 临猗县| 阿拉善盟| 沂水县| 广昌县|