posts - 188,comments - 176,trackbacks - 0

           

          “沒有人喜歡bug。”大多數關于單元測試的文章以這句話開篇。的確,我們都希望代碼如設計的那樣準確地執行,但是就好像叛逆孩子一樣,程序在完成之后產生的行為將難以控制。比那些家長們幸運的是,我們可以運用工具以確保程序達到預期效果。

          市面上有很多用于測試,分析以及debug程式的工具,其中以JUnit最為有名。這是一個協助軟件工程師,QA(品質監管)工程師測試階段性代碼的平臺。幾乎每個接觸過JUnit的人都對它有強烈的感情:要么喜歡,要么討厭。主要的抱怨之一是它缺少做復雜場景測試的能力。

          通過突破傳統模式的思考,這一問題可以得到解決。這篇文章將介紹JUnit如何利用Pisces來實現復雜測試。Pisces是一個開源項目,作為JUnit的擴展,它可以讓你寫出由一些JUnit測試組成的測試單元,每個測試單元可以以串行或并行的方式運行在一個遠程主機上。Pisces可以讓你構成、運行復雜場景,并在一個地點協調它們。

          JUnit基礎
          JUnit中有兩個基本對象,TestCase和TestSuite。TestCase通過提供一組方法來實現一系列測試。例如,setup()方法用來在每項測試開始前建立測試所需的測試環境,而teardown()方法用來在測試后銷毀該環境。其他的方法都會完成各式各樣的任務,例如,在測試中進行性能檢測,判斷變量是否為null,比較變量以及捕捉異常。
          要創建測試程序,需要繼承TestCase類,覆寫setup和teardown方法,然后添加自己的測試函數,這些函數通常以“test測試名”的形式命名。

          下面是一個測試程序的例子:
          public class MyTestCase extends TestCase {

              /**
              * call super constructor with a test name
              * string argument.
              * @param testName the name of the method that
              * should be run.
              */
              public MyTestCase(String testName){
                  super(testName);
              }
              
              /**
              * called before every test
                  *  在每項測試開始前被調用
              */
              protected void setUp() throws Exception {
                  initSomething();
              }
                  
              /**
              * called after every test
                  *  在測試完成后被調用
              */
              protected void tearDown() throws Exception {
                  finalizeSomething();
              }
                  
              /**
              * this method tests that ...
              *  這個方法測試……
              */
              public void testSomeTest1(){
              ...
              }
                  
              /**
              * this method tests that ...
                  *  這個方法測試……
              */
              public void testSomeTest2 (){
                  ...
              }
          }

          TestSuite是由幾個TestCase或其他的TestSuite構成的。你可以很容易的構成一個樹形測試,每個測試都由持有另外一些測試的TestSuite來構成。被加入到TestSuite中的測試在一個線程上依次被執行。
          ActiveTestSuite是TestSuite的一個子類。被添加到ActiveTestSuite中的測試程序以并行方式執行,每個測試在一個獨立的線程上運行。創建一個測試單元的方法之一是繼承TestSuite類并覆寫suite()方法。

          下面是一個簡單的例子:
          public class MyTestSuite extends TestSuite {
              public static Test suite() {

                  TestSuite suite =
                      new TestSuite("Test suite for ...");
                          
                  // add the first test
                  // 添加第一個測試
                  MyTestCase mtc =
                      new MyTestCase("testSomeTest1");
                  suite.addTest(mtc);
                          
                  // add the second test
                  // 添加第一個測試
                  MyTestCase mtc2 =
                      new MyTestCase("testSomeTest2");
                  suite.addTest(mtc2);
                          
                  return suite;
                          
              }
                  
          }

          運行一個測試或測試單元非常簡單,因為從JUnit提供的GUI開始,到所有的IDE開發環境,例如Eclipse,都有GUI可以使用。
          圖1, 顯示了TestSuite在Eclipse中是如何表示的。

          image


          圖1,集成在Eclipse中的JUnit


          因為介紹JUnit并不是這篇文章的主題,而且有很多關于JUnit的文章,本文就只提供這些JUnit基礎概念的概要。在“資源”小節里有對JUnit更深入介紹的文章。

          JUnit的優點和缺點
          JUnit是一個易用的,靈活的,開源的,測試平臺。就像所有其他項目一樣,它有很多優點,但也有不足之處。通過使用無需人工干預的JUnit自動測試平臺,我們很容易累積起大量的JUnit測試程序從而保證以往的bug不會重現。另外,JUnit便于和編譯單元(如,Ant)以及IDE單元(如,Eclipse)集成。

          JUnit的弱點也眾所周知。它僅支持同步測試,而且不支持重現和其他異步單元。JUnit是一個黑箱測試平臺,因此測試那些不會直接影響功能的bug(例如,內存泄漏)就非常困難。除此之外,它不支持易用的腳本語言,因此,想要使用JUnit就要懂得Java。

          JUnit的另一個不足是JUnit測試被限制于一個JVM之上。當要測試復雜或分布式場景的時候,這就變成個大問題。本文剩下的部分,就這個問題及其解決方法進行論述。

          復雜場景測試:
          我們為什么需要測試復雜的分布式場景?
          1. 那些確保小單元的完整性的測試很有用,但同時也有局限性。經驗告訴我們,大多數bug是在完整的測試中被發現的。這些問題從兩個模塊不能一同正常協調工作,到兩個獨立應用程序的異常。無論這是兩個應用服務,還是客戶/服務環境,甚至是點對點模式,對這些復雜場景的測試尤其重要,因為那些難纏的bug往往寄生于此。但是用JUnit對此幾乎無能為力。
          2. 雖然Java具有平臺無關性,但測試一個應用程序在多種操作系統上的表現還是一個明智的選擇。你的程序可能在所有的操作系統上都能運行,但是卻不一定嚴格地按照你設計的那樣正常工作。在所有的操作系統上重復同樣的一組測試程序是一件耗時的工程,而用JUnit你不能進行分布式測試,因此你無法讓同樣的一組測試同時運行在幾個JVM之上,而每個JVM運行在不同的操作系統之上。
          3. 一些單元代碼,只能在多JVM場景下被測試。例如,測試建立一個連接(TCP socket或者HTTP連接)以及從中取得的信息的完整性。這樣的測試不可能(或者說很難)在單一JVM上測試。而這是JUnit給我們的唯一選擇。

          用Ant協同測試
          僅使用JUnit和Ant來協調幾個運行在不同JVM上的測試是可能的。Ant可以以并行或者串行的方式執行任務(使用<parallel>標記),而且可以設置當有任何測試失敗的時候,就停止運行。
          但這種方法有其局限性,首先,使用JUnit和Ant仍然把你限制在一個操作系統之上。其次,隨著你的測試程序的累積,你會發現Ant XML文件會變得越來越大,以至于無法管理。第三,我們發現在某些情況下,分支JVM會在Ant任務結束后保留下來甚至繼續運行。
          Pisces項目就是為了解決JUnit的這些限制而來的,它給予JUnit復雜場景和分布式測試的能力。本文下面的章節將介紹這個開源項目。

          利用Pisces打破JUnit的局限
          Pisces基礎知識
          Pisces是一個開源項目,它擴展了JUnit平臺。就像許多其他的擴展程序一樣,Pisces添加了新功能的同時也保證了擴展前后JUnit操作的一致性。

          Pisces的核心是在同一主機或不同主機上實現在遠程JVM上運行JUnit測試的能力。這些遠程測試程序會封裝在本地運行的JUnit測試程序中,因此開發人員或者QA(品質測試)人員可以用通常的JUnit GUI工具來運行這些通常(本地)的測試程序。用來包裝遠程測試的對象叫做RemoteTestCase,它也是TestCase冊子類。

          圖2顯示的是遠程測試程序和它的包裝器。

          image


          圖2,遠程測試和它的包裝器


          在每一個遠端,我們運行一個Pisces代理程序,并指定唯一的代理名。這個代理負責運行實際的JUnit測試程序并將結果返回到本地。現在,一旦我們能運行一個包裝在本地測試中的遠程測試程序,我們就能通過組合幾個這樣的測試來創建一個更為復雜的場景。

          image


          圖3, 由幾個遠程測試組成的測試單元



          改變默認輸出
          每個代理運行一組測試程序,而每個測試程序都可能寫信息到默認輸出。因為保證測試人員或開發人員在測試過程中得到這些信息很重要,所以默認輸出被拷貝到本地測試單元的控制臺中。這樣,便可以避免在測試單元中查看每個代理的控制輸出。

          Pisces的可擴展通信層
          在各個代理及本地主測試程序之間的通信是件復雜的事情,因為Pisces必須能在各種不同的環境以及網絡配置下正常工作。為了解決這一問題,Pisces有一個可以擴展的通信層。它的每一個實現解決一個指定的網絡環境下的問題。

          Pisces提供兩個默認的基本通信層,一個是易于配置但只能工作在局域網內的multicast實現,另一個是JMS實現。它需要安裝面向消息中間件/消息導向中間件(MOM, Message-Oriented Middleware),可以應付大多數網絡環境。

          配置并運行Pisces測試
          1.配置并運行Pisces代理
          正如前面所提到的,Pisces測試單元是由幾個運行在遠程代理之上的Junit測試程序組成的。每個代理都是一個Java應用程序,它根據從主測試程序接收的指令來運行JUnit測試,并將結果及默認輸出返回到主測試單元。

          運行代理程序最簡單的方式是在Pisces提供的腳本文件夾中配置并運行相關的可執行腳本。此外,你也可以在已經提供的腳本的基礎上構建你自己的腳本。腳本文件容許用戶配置代理的通用參數,例如唯一標識符,為通信層指定multicast IP地址和端口。

          下面是一個代理程序的腳本文件,該代理程序提供了通信層,并運行在Linux系統上:(下面是一個運行在Linux系統上的,具有通信層的代理程序的腳本文件)

          #!/bin/sh

          # the folder were the agent can find junit.jar
          export JUNIT_HOME=/opt/junit3.8.1

          # the folders were the agent can find
          # the junit tests
          export JUNIT_TEST_CLASSPATH=../examples/

          # the multicast port that the communication
          # layer uses
          export PISCES_MCP=6767

          # the multicast IP that the communication
          # layer uses
          export PISCES_MCIP=228.4.19.76

          # the unique name of the agent
          export AGENT_NAME=remote1

          java -classpath
              "$JUNIT_HOME/junit.jar:../pisces.jar:$JUNIT_TEST_CLASSPATH" \
              org.pisces.RemoteTestRunnerAgent \
              -name $AGENT_NAME -com multicast \
              -mcp $PISCES_MCP  -mcip $PISCES_MCIP



          利用JMS通信層的Pisces代理的可執行腳本和multicast方式的差不多是一樣的。JMS通信層以裝載類的名字作為參數,該裝載類返回你的JMS提供者的ConnectionFactory。

          2.配置并運行Pisces-Enabled測試單元
          在配置并運行了所有相關代理后,我們還需要配置并運行我們的Pisces-enabled測試單元。
          首先,我們需要把通信層的配置添加到我們的TestSuite的開頭。顯示如下:

          // create the multicast communication layer
          MulticastRemoteTestCommunicationLayer com =
              new MulticastRemoteTestCommunicationLayer(
                 RemoteTestRunner.name ,"228.4.19.76",6767);

          // set the communication layer
          RemoteTestRunner.setCom(com);

          接下來,我們需要創建一個TestSuite的實例,并添加想要遠程調用的測試。這一步驟可以通過指定JUnit類(有一個可選的測試方法)和將要運行這一特定測試的代理的名字來實現。
          下面是一段示例代碼:

          // 創建一個通常的TestSuite實例
          TestSuite suite =
              new TestSuite("Test for org.pisces.testers");

          // 為這個實例創建一個包裝器
          RemoteTestCase rtc =
              RemoteTestCase.wrap(RemoteTestTestCase.class
                  ,"someTestMethod", "remote1");
                  // 添加測試實例到TestSuite
          suite.addTest(rtc);

          文章的下一節,將提供一個分布式測試的例子和TestSuite的代碼。

          示例:并發登錄測試
          假設我們有一個網絡應用程序。一個用戶登錄后,獲得服務,然后退出。我們想確認我們的安全模塊能夠阻止單一用戶在兩臺不同的電腦上同時登錄。

          我們創建了一個名字為MyTestCase的測試對象,正如前面所表示的,我們有兩個測試方法。第一個,testLogin(),用來確認我們第一次能正確登錄。第二個方法,testLoginFails(),用來確認第二次登錄時會失敗。

          下面是利用了Pisces的測試單元:
          public class RemoteTestSuiteExample extends TestSuite {
              public static Test suite() {
                  // 配置并啟動通信層
                  JMSRemoteTestCommunicationLayer com = null;
                  try {

                      com =
                          new JMSRemoteTestCommunicationLayer
                              (RemoteTestRunner.name,
                                  new MantaConnectionFactory());
                  } catch (JMSException e) {
                      throw new RuntimeException(e);
                  }
                  RemoteTestRunner.setCom(com);

                  // 實例化JUnit
                  TestSuite suite =
                      new TestSuite("Test for org.pisces.testers");
                  
                  // 在遠程代理remote1上運行MyTestCase類中的testLogin           RemoteTestCase rtc =  
                  RemoteTestCase.wrap(MyTestCase.class,
                          "testLogin", "remote1");
                  suite.addTest(rtc);

                  // 在遠程代理remotel2上運行MyTestCase類中的testLoginFails方法
                  RemoteTestCase rtc1 = RemoteTestCase.wrap(MyTestCase.class,
                          "testLoginFails", "remote2");
                  suite.addTest(rtc1);
                  return suite;
              }
          }

          如果一切正常,遠程代理(remote1)將成功登錄。當另外一個代理(remote2)試圖以同一個用戶名和密碼登錄的時候,將失敗,因為該用戶已經在另外一臺電腦上登錄了。
          這個測試可以更為復雜。例如,通過添加testLogOut()方法來實現其他的安全測試,但是作為一個示例程序,我希望其保持簡單。

          在這個例子中,我利用了叫做MantaRay的無服務JMS,它是一個開源的,消息中間件包,基于點對點的技術。在最新一版的Pisces中,你可以找到幾個利用JMS通信層的例子和利用multicast通信層的例子。

          結論
          Bug并不好玩,但是借助JUnit,一個被廣泛利用的,開源的,自動測試的平臺,運行多重測試變得容易了許多。許多項目擴展了JUnit的功能,但是到目前為止,測試還被局限在一臺機器上。有了Pisces的輔助,和一些不同以往的思考方式,我們終于可以創建復雜的,分布式測試了。

          http://www.onjava.com/pub/a/onjava/2005/07/13/pisces.html

          posted on 2007-05-24 11:46 cheng 閱讀(680) 評論(0)  編輯  收藏 所屬分類: Junit

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


          網站導航:
           
          主站蜘蛛池模板: 靖边县| 罗田县| 桦甸市| 五寨县| 屯门区| 棋牌| 明星| 华宁县| 栖霞市| 岗巴县| 翁牛特旗| 吴桥县| 满洲里市| 清涧县| 石渠县| 林芝县| 亚东县| 南溪县| 吐鲁番市| 威宁| 昌吉市| 融水| 平果县| 宁远县| 高阳县| 渭源县| 中西区| 吴忠市| 衡南县| 宁河县| 类乌齐县| 永泰县| 西盟| 长沙市| 迭部县| 商都县| 金乡县| 敦煌市| 环江| 海门市| 鹰潭市|