1. 程式人生 > >JUnit學習筆記11---用Cactus進行容器內測試

JUnit學習筆記11---用Cactus進行容器內測試

In-container testing with Cactus                        Good design at good times.Make it run,make it run right.----Kent Beck

本章內容:

  • 對元件進行單元測試時使用mock objects的缺點
  • 介紹使用Cactus進行容器內測試
  • Cactus的工作原理

      對元件進行單元測試的問題

    想象一下,有個Web應用程式,它使用了servlet,你希望對SampleServlet servlet的isAuthenticated方法進行單元測試。

package
junitbook.container; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; public class SampleServlet extends HttpServlet { public boolean isAuthenticated(HttpServletRequest request) { HttpSession session = request.getSession(false
); if (session == null) { return false; } String authenticationAttribute = (String) session.getAttribute("authenticated"); return Boolean.valueOf( authenticationAttribute).booleanValue(); } }

         為了能夠測試這個方法,你需要得到一個合法的HttpServletRequest物件。不幸的是,不可能呼叫new HttpServletRequest來建立一個可用的請求。HttpServletRequest的生命週期是由容器管理的。無法單獨的使用JUnit為isAuthenticated方法編寫測試。

  定義   元件/容器  ---元件是在容器內執行的一段程式碼。容器則是在位存放在內的元件提供有用服務(比如生命週期、安全、事物、分佈等等)的器皿。

        幸運的是我們可以有幾種解決方案,概括的說有兩種核心的解決方案:用mock objects進行容器外測試和用Cactus進行容器內測試。

       這兩種方法的一個變體就是stub。mock和Cactus都是可行的,接下來將討論兩種實現,以及兩者的比較。

    用mock objects測試元件

    我們將試著用mock來測試servlet,然後討論這種方式的優劣。有幾個框架可以生成mock objects,本章中我們將使用EasyMock。

    EasyMock是現在最著名的mock objects生成器之一。EasyMock是用java的動態代理(dynamic proxies)實現的,所以可以透明的執行時生成mock objects。

      用EasyMock來測試servlet例子

       我們想到的對isAuthenticated方法進行單元測試的方法是用第七章所描述的mock objects方法來模擬HttpServletRequest類。這種方式是可以工作的,但是你要編不少的程式碼。如果是使用EasyMock框架,那麼我們可以輕鬆的做到這一點!

package junitbook.container;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.easymock.MockControl;

import junit.framework.TestCase;

public class TestSampleServlet extends TestCase
{
    private SampleServlet servlet;
    
    private MockControl controlHttpServlet;                 ;建立兩個mock objects,一個是HttpServletRequest,另一個是HttpSess
    private HttpServletRequest mockHttpServletRequest;      ;ion。EasyMock通過為mock建立動態代理來工作,動態代理是通過MockContr
                                                            ;ol控制物件來控制的。
    private MockControl controlHttpSession;                 ;
    private HttpSession mockHttpSession;                    ;
    
    protected void setUp()
    {
        servlet = new SampleServlet();
        
        controlHttpServlet = MockControl.createControl(
            HttpServletRequest.class);
        mockHttpServletRequest = 
            (HttpServletRequest) controlHttpServlet.getMock();
        
        controlHttpSession = MockControl.createControl(
            HttpSession.class);
        mockHttpSession = 
            (HttpSession) controlHttpSession.getMock();
    }

    protected void tearDown()
    {
        controlHttpServlet.verify(); 讓EasyMock來校驗確實是呼叫了被模擬的方法,如果你定義了一個mock,但是卻沒有被呼叫,那麼將會
        controlHttpSession.verify(); 報錯。如果你沒有為一個方法定義任何的行為,但是它被呼叫了,那麼也會報錯。這是在tearDown方法中   
    }                              ;做的。這樣就可以對所有的測試自動校驗。
    
    public void testIsAuthenticatedAuthenticated()
    {
        mockHttpServletRequest.getSession(false);           ;使用控制物件告訴HttpServletRequest mock object,當getSession
        controlHttpServlet.setReturnValue(mockHttpSession);  (false)方法被呼叫時,這個方法應當返回一個HttpSession mock object

        mockHttpSession.getAttribute("authenticated");      ;這裡和上面一樣,定義了方法被呼叫後的返回情況,這種是EasyMock的訓練模
        controlHttpSession.setReturnValue("true");          ;式(training mode)
        
        controlHttpServlet.replay();       一旦你激活了這的mock object,你就走出了訓練模式,呼叫mock objects會返回預期的結果。
        controlHttpSession.replay();
                
        assertTrue(servlet.isAuthenticated(mockHttpServletRequest));
    }

    public void testIsAuthenticatedNotAuthenticated()               ;檢測servlet中沒有authenticated時
    {
        mockHttpServletRequest.getSession(false);
        controlHttpServlet.setReturnValue(mockHttpSession);

        mockHttpSession.getAttribute("authenticated");
        controlHttpSession.setReturnValue(null);
        
        controlHttpServlet.replay();       
        controlHttpSession.replay();
                
        assertFalse(servlet.isAuthenticated(mockHttpServletRequest));
    }

    public void testIsAuthenticatedNoSession()                      ;檢測沒有Session時
    {
        mockHttpServletRequest.getSession(false);
        controlHttpServlet.setReturnValue(null);
       
        controlHttpServlet.replay();       
        controlHttpSession.replay();
                
        assertFalse(servlet.isAuthenticated(mockHttpServletRequest));
    }

}
    用了上面的方法,我們在回憶一下在前面筆記中記錄的情況,這正是個玩笑,因為,前面沒有使用EasyMock時,我們運用很多模式去重構程式碼,緊在這
裡看來是不必要的,而重構程式碼有各種好。
     接下來分析一下使用mock objects的優勢和不足,最大的優點就是,執行測試時可以不需要執行容器。很快就能建立起測試,而且執行的速度也很快。
主要的缺點是:元件並不是在部署他們的容器中進行測試的,你無法測試元件和容器的互動,而這也是程式碼的重要部分。你也沒有測試運行於容器內部的不同
元件之間的互動。運用mock還有其他的缺點,比如大量的mock將帶來大量的開銷,而且在測試時一定要知道被模擬元件的API,很多API是不那麼好理解的。
幸運的是,有一種技術來彌補這些不足---Cactus。
什麼是整合單元測試
   在容器內進行單元測試,可謂是一舉兩得。整合單元測試介於邏輯單元測試和功能單元測試之間。下圖說明了關係:
    看不到圖點這裡>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

從另外的一個角度看,整合測試就是邏輯單元測試和功能單元測試的一般化。如果你把測試的開始位置設在某個方法中,將測試的結束位置設在同一個方法中,那麼結果就是邏輯單元測試。如果你將開始的位置和結束的位置放在應用程式的某個入口,那麼結果就是功能測試。

主要的含義就是,沒什麼東西是全黑或是全白的,有時候,你要管理應用程式需要更多的靈活性,有時候,你的測試介於二者之間。

   在下面介紹Jakarta Cactus,一個專門用來對伺服器端java元件進行整合單元測試的框架。

  介紹Cactus

  Cactus是針對整合單元測試的,Cactus的遠景基本是這樣的:在過去今年裡,java程式碼轉向元資料,對元件的編寫者,提供的服務越來越透明,最近的趨勢是使用AOP程式設計,實現程式設計同業務程式碼解耦。基本業務程式碼在減少,但是元資料配置在增多,整合測試變得越來越重要,Cactus對整合測試是擁抱的態度,將之融入開發週期,並且提供細粒度的解決方案。

  用Cactus測試元件

測試程式碼如下:

package junitbook.container;

import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;

public class TestSampleServletIntegration extends ServletTestCase
{
    private SampleServlet servlet;    

    protected void setUp()
    {
        servlet = new SampleServlet();
    }
    
    public void testIsAuthenticatedAuthenticated()
    {
        session.setAttribute("authenticated", "true");
        
        assertTrue(servlet.isAuthenticated(request));
    }

    public void testIsAuthenticatedNotAuthenticated()
    {
        assertFalse(servlet.isAuthenticated(request));
    }

    public void beginIsAuthenticatedNoSession(WebRequest request)
    {
        request.setAutomaticSession(false);
    }
    
    public void testIsAuthenticatedNoSession()
    {
        assertFalse(servlet.isAuthenticated(request));
    }
}

Cactus框架將容器物件(這裡是HttpServletRequest和HttpSession)提供給測試。這樣就使其變得快捷和簡單!

  執行Cactus

前面我們提到了Jetty,那麼使用Jetty+Cactus是非常不錯的選擇,他們的好處是可以運用在任何的IDE上!

   為了通過Jetty執行Cactus測試,你需要建立一個JUnit test suite,它要包含測試的內容,而且還要用Cactus提供的JettyTestSetup類包裝起來。

package junitbook.container;

import org.apache.cactus.extension.jetty.JettyTestSetup;

import junit.framework.Test;
import junit.framework.TestSuite;

public class TestAllWithJetty 
{
    public static Test suite()
    {
        System.setProperty("cactus.contextURL",
            "http://localhost:8080/test");

        TestSuite suite = new TestSuite("All tests with Jetty");
        suite.addTestSuite(TestSampleServletIntegration.class);
        return new JettyTestSetup(suite);
    }
}

為了使程式能夠執行,我們還不得不做一些工作,將Cactus jar、Commons Logging jar、Commons HttpClient jar、AspectJ runtime jar和Jetty jar加入到我們的工作jar庫中!

容器內測試的缺點:

  • 需要特定的工具
  • 更長的執行時間
  • 複雜的配置

   Cactus如何工作

    下面的章節將是本書最終要達成的目標,也是為了完成目標,我們必須對Cactus的工作原理有一定的瞭解!

    182755_2009110118415114syB                          

   第一步,執行beginXXX

                如果存在beginXXX方法,那麼Cactus就會執行這個方法。這個beginXXX方法讓你把資訊傳遞給redirector。TestSampleServletIntegration

例子繼承了ServletTestCase並且連線到Cactus servlet redirector。servlet redirector被實現為一個servlet,這是容器的入口。Cactus客戶端通過開啟HTTP連線來呼叫servlet redirector。beginXXX方法會建立Http相關的引數,這些引數在servlet redirector所收到的http請求中設定。這個方法可以用來定義http post/get引數、http cookie以及http header等等。例如:

              public void beginXXX( WebRequest request)

                {request.addParameter(“param1”,”value1”);

                 request.addCookie(“cookie1”,”value1”);

                   }

                     在TestSampleServletIntegration類中,我們用beginXXX方法來告訴redirector方法不要建立HTTP session

         public void beginIsAuthenicatedNoSession(WebRequest request){
          request.setAutomaticSession(false);
          }
 第二步,開啟redirector連線
        YYYTestCase開啟到redirector的連線。在這個例子中,ServletTestCase程式碼開啟到servlet redirector的http連線。
 第三步,建立伺服器端的TestCase例項
        Redirector會建立YYYTestCase的一個例項,注意這是Cactus建立的第二個例項,第一個是在客戶端建立的。之後redirector類獲得了物件的實
例,並且通過設定類變數把他們賦給YYYTestCase例項。
  第四步,在伺服器端呼叫setUp、testXXX、tearDown
  第五步,執行endXXX
  最後一步,蒐集測試結果
小結: 要對容器應用程式進行單元測試,採用純JUnit就顯得力不從心了,我們採用
mock objects可以應付自如,但是,我們這樣做就缺少了一些測試,而今,我們有了
Cactus就可以解決mock不能解決的問題了!