隨筆-55  評論-208  文章-0  trackbacks-0
          不好意思,最近由于在趕項目所以這篇文章今天才有時間寫出來

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

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

          然后,根據(jù)上述設(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         //得到待測方法結(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        //進行斷言判斷期望和實際結(jié)果
          28        assertEquals(outPut.toString(),result.toString());
          29    }

          此時,有關(guān)getCatalogLayout的測試方法已經(jīng)寫完了,但是實際上這個方法我們還沒有寫呢。所以在eclipse中會顯示錯誤我們使用eclipse的自動完成功能來在標(biāo)簽中實現(xiàn)一個空getCatalogLayout方法,下面我將寫getCatalogLayout方法的實現(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;
              }

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

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

              
          public static void setSpringContext(ServletContext servletContext) {

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


          }

          這個類中的幾個方法可以對mock的pagecontext或者servletcontext加載spring的上下文

          這里簡單介紹一下我使用的環(huán)境模擬工具:
          我沒有使用流行的mockObject和MockRunner,主要是因為我的項目基本框架是spring+Struts,而對這兩個框架的模擬有更有針對性地springmock和strutstestcase,這樣在我需要加載struts相關(guān)配置信息時,strutstestcase提供了更好的支持。
          比如我這里需要使用到strust中配置好的properties信息,那么下面各測試基類就基本實現(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 對于tag設(shè)施struts的配置 同時加載spring的配置
               * 
          @param tag
               
          */

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

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


              
          /**
               * 
          @author rocket 測試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中來,我要開始對getOutWml進行測試了,在測試這個方法之前我們需要對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);
                  
          //這里針對tag加載相關(guān)的struts和spring環(huán)境
                  setStrutsContextInTag(sct);
                  
          //得到實際結(jié)果
                  StringBuffer result = sct.getOutWml();
                  logger.debug(result);
                  
          //獲得期望結(jié)果
                  List catalogList = ChannelMGR.getCatalogsByChannelAndParentId(channel,
                          parentid);
                  
          //由于getCatalogLayout已經(jīng)測試過,所以getCatalogLayout方法可以直接調(diào)用
                  StringBuffer expect = sct.getCatalogLayout(catalogList);     
                      //斷言判斷
                     assertEquals(expect ,result );
              }

          為了簡化測試環(huán)境設(shè)置代碼,所以我在BaseTagTest中定義好了setStrutsContextInTag方法用于加載spring和struts的相關(guān)信息
           下面針對getoutWml的測試我可以很快得到實現(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,然后得到一個漂亮的綠條。當(dāng)你沒有得到green bar時首先要檢查的是你的測試邏輯和測試環(huán)境是否正確,然后再檢查你的實現(xiàn)代碼是否正確。

          到此為止,我的ShowCatalogTag就已經(jīng)開發(fā)完成了。有人也學(xué)會問怎么沒有沒有見到你的doStartTag這個方法呢,是這樣的我封裝了一個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;
              }


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

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

           


           

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

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

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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 喜德县| 临汾市| 玛纳斯县| 河西区| 华宁县| 荃湾区| 普陀区| 利辛县| 格尔木市| 五大连池市| 江达县| 墨江| 喀喇沁旗| 淳化县| 晋宁县| 潜山县| 宁晋县| 邵武市| 昌平区| 弋阳县| 许昌市| 专栏| 漠河县| 三台县| 哈密市| 六安市| 登封市| 灌南县| 湖口县| 辽中县| 定陶县| 清新县| 时尚| 天水市| 黔南| 文化| 扎鲁特旗| 长丰县| 榆树市| 昌平区| 惠州市|