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

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

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

    JAVA—咖啡館

    ——歡迎訪問rogerfan的博客,常來《JAVA——咖啡館》坐坐,喝杯濃香的咖啡,彼此探討一下JAVA技術,交流工作經驗,分享JAVA帶來的快樂!本網站部分轉載文章,如果有版權問題請與我聯系。

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      447 Posts :: 145 Stories :: 368 Comments :: 0 Trackbacks
    【IT168 技術文章】

      引言

      一個例子

      我們先看一個例子,以了解對”規則”做單元測試的特點。我們有一個性能調優工具 WPA, 它能夠將與性能相關的參數的值進行評估并推薦最優值。它的評估和推薦最優值算法都是基于”規則”的。

      Java 虛擬機的初始堆大小(JVM initial heap size)是一個影響 JVM 的性能的關鍵參數。性能調優工具 WPA 有一套規則對“ JVM initial heap size ”的值進行評估(參見清單 1)。評估的結果有 5 個級別。級別“ 1 ”表示設置良好,可提高性能;級別“ 5 ”表示設置很差,會降低性能。

      清單 1. JVM initial heap size rating algorithm        在這一套規則中,包含很多不同的條件(見“ IF-ELSE ”語句)。在測試時(單元測試和功能測試),我們需要至少 24 組測試數據以覆蓋所有的閥值(threshold value)和等價類(equivalent class)。參見表 1。

    1 Rating3UpperBounds = 1024                                  
    2 Rating3LowerBounds = 48  
    3 Rating5UpperBounds = 1536                                  
    4 Rating5LowerBounds = 32
    5 Rating3Multiplier = 4
    6 Rating5Multiplier = 3  
    7
    8 absoluteMaximumValue= Math.min(currentMemoryPoolSize, overallMemoryOnPartition)
    9     / Rating3Multiplier
    10 if (initialHeapSize > absoluteMaximumValue) {
    11      return 4;
    12 }
    13 if ((initialHeapSize < Rating5LowerBounds) ||
    14       (initialHeapSize > Rating5UpperBounds)) {
    15       rating = severe problem (5)
    16   }
    17   else if ((initialHeapSize < Rating3LowerBounds) ||
    18            (initialHeapSize > Rating3UpperBounds)) {
    19       rating = probable problem (3)
    20   }
    21 ……
    22 }
    23 if (initialHeapSize * Rating5Multiplier > currentMemoryPoolSize)
    24 {
    25     return severe problem (5)
    26 }
    27 else if(initialHeapSize*Rating3Multiplier > currentMemoryPoolSize)
    28 {
    29     return max(rating, 3)
    30 }
    31 else if(initialHeapSize*Rating2Multiplier > currentMemoryPoolSize)
    32 else {
    33     return max(rating, 1)
    34 }

      對”規則”做單元測試

      從“JVM initial heap size rating algorithm”以及 WPA 中其他基于“規則”的性能調優算法,我們總結出對“規則”做單元測試的特點有:

      一、為了覆蓋所有的閥值 (threshold value )和等價類 (equivalent class ),我們需要大量測試數據。單元測試的通常做法是,把所有的測試數據寫入測試代碼中。對比以格式化的形式(XML,Excel 等)來保存測試數據,這樣做使得這些數據不容易維護和復用。

      二、由于對”規則”的測試涉及到變量,這些變量來自運行時的輸入,我們在單元測試之前就需要構建運行時環境,這種工作可能非常復雜。如果一套”規則”中包含更多的條件和輸入參數,以上兩個問題會更加嚴重

      三、在一個基于”規則”的系統里,”規則”之間有很多共性,我們沒有必要對每一個”規則”都寫一個測試類。

      本文將給出解決以上問題的一種做法。本文的組織結構如下:

      編寫 Mock 類:利用 Mock 對象來代替實時運行環境;

      將測試數據保存到配置文件中:利用格式化文檔實現測試數據的復用性和可維護性;

      編寫 SettersMap 類:這個類保存了配置文件中的數據并提供了獲取這些數據的接口;

      編寫可復用的 TestCase 類:創建 JUnit 的擴展類以適應對“規則”做單元測試的需求;

      用 TestSuite 組織測試用例:用 TestSuite 把測試用例組織起來;

      在以下內容中,我們將拿“ JVM initial heap size rating algorithm ”做例子。

    為了測試” JVM initial heap size rating algorithm ”,我們需要獲得三個輸入參數。然而,獲取這三個參數并不是那么容易。

      為了簡化測試環境,我們利用 Mock 對象來設置這些參數。

      Mock 對象是單元測試經常用到的一種技術,Mock 對象能模擬實際對象的行為,并且提供了額外的行為控制接口。還有一個常用到的詞是 Dummy 對象。 Mock 和 Dummy 的含義經常被混淆。在這里,我們認為 Dummy 對象沒有提供額外的行為控制接口。

      對于” JVM initial heap size rating algorithm ”,我們需要一個 Mock 類,它的行為與“ InitialHeapSize.java ”相同(“ InitialHeapSize.java ”是 “ JVM initial heap size rating algorithm ”的 Java 代碼)。我們把這個 Mock 類命名為“ MockInitialHeapSize.java ”。一個 Client 類可以把“ initialHeapSize ” , “ currentMemoryPoolSize ” , 和“ overallMemoryOnPartition ” 直接設置到“ MockInitialHeapSize ”對象中。參見清單 2

      清單 2. MockInitialHeapSize.java

    1 public class MockInitialHeapSize extends InitialHeapSize {
    2     // 設置 InitialHeapSize
    3     public void setInitialValue(String initialValue){
    4         this.initialValue = initialValue;
    5     }
    6     // 設置 MemoryPoolSize
    7     public void mockSetMemoryPoolSize(String size) {
    8         try{
    9             this.currentSettingOfMemoryPoolSize=Float.parseFloat(size);
    10         }catch(NumberFormatException ne){
    11             Advisor.getLogger().severe("size: "+size+" are not an float value.");
    12         }
    13     }
    14     // 設置 OverallMemory
    15     public void mockSetOverallMemory(String size) {
    16         try{
    17             this.overallMemoryOnPartition=Float.parseFloat(size);
    18         }catch(NumberFormatException ne){
    19             Advisor.getLogger().severe("size: "+size+" are not an float value.");
    20         }
    21     }
    22     ……
    23 }

      將測試數據保存到配置文件中

      正如我們在文章開頭提到的,我們希望把測試數據保存成格式化的形式,以便對這些數據進行維護和復用。表 1展示了用一個 Excel 文件 “ MockInitialHeapSize_rating.xls ” 保存所有的測試數據的例子。 這個文件完全可以用于功能測試的文檔編寫。

      表 1. JVM initial heap size 測試數據
    setInitialValue mockSetOverallMemory mockSetMemoryPoolSize result
    31 92 92 5
    31 123 123 5
    31 124 124 5
    32 95 95 5
    32 127 127 3
    32 128 128 3
    47 140 140 5
    47 187 187 3
    47 188 188 3
    48 143 143 5
    48 191 191 3
    48 192 192 1
    49 146 146 5
    49 195 195 3
    49 196 196 1
    1024 3071 3071 5
    1024 4095 4095 3
    1024 4096 4096 1
    1025 3074 3074 5
    1025 4009 4009 3
    1025 4100 4100 3
    1537 4610 4610 5
    1537 6147 6147 5
    1537 6148 6148 5

      表 1中,每一行都代表了一組測試數據,包括輸入參數和期望結果。三個輸入參數“initialHeapSize”,“currentMemoryPoolSize”,“overallMemoryOnPartition”分別保存到了三列中:“setInitialValue”,“mockSetOverallMemory ”和“mockSetMemoryPoolSize”。期望結果保存到了“result”列 ,測試代碼將從這個文件中獲取測試數據。

      配置文件的格式是可以變化的,只需要提供相應的 SettersMap 和 SettersMapFactory 類就可以了。

     有了配置文件,我們需要編寫代碼從配置文件中讀取測試數據。我們用一個接口類“SettersMap”來代表一個配置文件。參見圖 1。附件“rule_test.zip”中的 BaseSettersMap.java 是 SettersMap 接口的一個實現。

      圖 1. SettersMap.java

      我們提供了一個工廠接口 SettersMapFactory 來構造 SettersMap 。這里采用了抽象工廠(Abstract Factory)的設計模式。

      清單 3. SettersMapFactory.java

    1 /*
    2 * Created on 2008-3-13
    3 *
    4 * TODO To change the template for this generated file go to
    5 * Window - Preferences - Java - Code Style - Code Templates
    6 */
    7 package attributetest.binding.spi;
    8
    9 import java.io.File;
    10
    11 /**
    12 * @author jsl
    13 *
    14 * TODO To change the template for this generated type comment go to
    15 * Window - Preferences - Java - Code Style - Code Templates
    16 */
    17 public interface SettersMapFactory {
    18     
    19      /**
    20      *
    21      * @return Factory 的名字
    22      */
    23      String getName();
    24     
    25      /**
    26      * 從配置文件創建 SettersMap ;
    27      * @param file 配置文件對應的 File 對象;
    28      * @return 根據配置文件創建的 SettersMap
    29      */
    30      SettersMap createSettersMap(File file);
    31     
    32      /**
    33      *
    34      * @return 配置文件的擴展名,如 ".xls", ".txt" 。通常,SettersMapFactory 的類型
    35      * 和配置文件的類型有一一對應的關系。
    36      */
    37      String getConfFileExtension();
    38 }

      對于不同的文件格式,需要提供不同的“ SettersMapFactory ”。附件“ rule_test.zip “中的“ ExcelSettersMapFactory.java ”是一個 Excel 格式的實現。

     在一個基于”規則”的系統里,”規則”之間有很多共性,我們沒有必要對每一個”規則”都寫一個測試類,而是希望能有一個通用的類,通過改變參數來測試不同的規則。

      標準的 JUnit 版本 (www.junit.org) 提供了 junit.framework.TestCase 類作為單元測試的一個最常用的入口。通常,我們有兩種方式來運行 TestCase:對象方式和類方式。在對象方式運行時,你需要 new 一個 TestCase 對象,并且在構造函數中指定 Test Method 的名字。運行時,只有這個 Test Method 會被調用。在類方式下,所有的以”test”開頭的方法都會被調用,但是我們無法復用這個類?!∵@兩種方式都不能滿足我們的需求。幸運的是,我們可以通過擴展“junit.framework.TestCase”來做到這一點。

      清單 4. ObjectTestCase.java

    1 package junit.extensions;
    2
    3 import java.lang.reflect.InvocationTargetException;
    4 import java.lang.reflect.Method;
    5 import java.lang.reflect.Modifier;
    6 import java.util.*;
    7
    8 import junit.framework.TestCase;
    9
    10 public class ObjectTestCase extends TestCase {
    11
    12      // 保存所有的 Test Method
    13      private ArrayList <Method> testMethods = new ArrayList();
    14
    15      /**
    16      * ObjectTestCase 在實例化時,把所有的 Test Method 保存到“ testMethods ”中。
    17      * @param name
    18      */
    19      public ObjectTestCase(String name) {
    20          super(name);
    21          Method[] allMethods = getClass().getDeclaredMethods();
    22          for (int i = 0; i > allMethods.length; i++) {
    23              Method method = allMethods[i];
    24              if (method.getName().startsWith("test")) {
    25                  testMethods.add(method);
    26              }
    27
    28          }
    29         
    30      }
    31     
    32      /**
    33      * ObjectTestCase 在實例化時,把所有的 Test Method 保存到“ testMethods ”中。
    34      */
    35      public ObjectTestCase() {
    36          Method[] allMethods = getClass().getDeclaredMethods();
    37          for (int i = 0; i > allMethods.length; i++) {
    38              Method method = allMethods[i];
    39              if (method.getName().startsWith("test")) {
    40                  testMethods.add(method);
    41              }
    42
    43          }
    44      }
    45
    46
    47
    48      @Override
    49      /**
    50      * 運行所有“ testMethods ”中保存的方法;
    51      */
    52      protected void runTest() throws Throwable {
    53
    54          for (int i = 0; i > testMethods.size(); i++) {
    55              Method method = testMethods.get(i);
    56              try {
    57                  method.invoke(this);
    58              } catch (InvocationTargetException e) {
    59                  e.fillInStackTrace();
    60                  throw e.getTargetException();
    61              } catch (IllegalAccessException e) {
    62                  e.fillInStackTrace();
    63                  throw e;
    64              }
    65
    66          }
    67      }
    68
    69      /**
    70      * @return "testMethods" 中保存的方法的個數
    71      */
    72      @Override
    73      public int countTestCases() {
    74          return testMethods.size();
    75      }
    76 }

      編寫 ObjectTestCase 類

      我們將構造一個“ObjectTestCase”類,這個類繼承了“TestCase”類。“ObjectTestCase”使用一個 ArrayList “testMethods” 來保存所有的 Test Method 。在實例化“ObjectTestCase”時,所有以“test”開頭的方法都會被注冊到“testMethods”中。在“runTest”時,所有的保存在 “testMethods”中的方法都會被調用 . 最后,別忘了復寫“countTestCases”以保證我們獲得正確的測試結果。

      編寫專用于“規則”的 AttirbuteTestCase 類

      有了“ObjectTestCase”類,我們就可以擴展它以獲得針對“規則”的“TestCase”類。圖 2 展示了這些類之間的關系。“AttributeTestCase”是一個抽象類,它繼承于“ObjectTestCase”。“testAttribute”是它的一個抽象方法,需要它的子類提供具體實現。這個方法會測試所有的數據。

      圖 2. TestCase Class Diagram

      “AttributeRatingTestCase”和“AttributeRecommendationTestCase”繼承了“AttributeTestCase”。以“AttributeRatingTestCase”為例,它的“testAttribute”方法首先獲得“SettersMap”,然后調用“setInput”把 SettersMap 中的數據設置到 Mock 對象中;最后,它調用 Mock 對象的“getRating”方法獲取結果。參見清單 5。我們在配置文件中,把每一列的列名設置為 Mock 對象的 Mock 方法名,這樣,測試框架就明確的知道應該調用 Mock 對象的什么方法來設置數據。為了做到這一點,撰寫配置文件時,必須知道相應的 Mock 方法名 ( 如 MockInitialHeapSize.mockSetMemoryPoolSize) 。由于我們在討論單元測試,我們認為測試人員擁有這些測試代碼,也就是知道 Mock 方法名。

      清單 5. AttributeRatingTestCase.java

    1 public class AttributeRatingTestCase extends AttributeTestCase {
    2
    3     public AttributeRatingTestCase(IRateable testAttribute,
    4         SettersMap settersMap);
    5     
    6     public void setUP();
    7
    8     public void testAttribute()throws Exception {
    9         this.results = new ArrayList();
    10         // 判斷要測試的對象是否是” IRateable ”,是則繼續,否則退出;
    11         if (this.testObject instanceof IRateable) {
    12             AttributeLogger.getLogger().info("******Test Rating of '"
    13                 + getSimpleTestObjectClassName() + "'******");
    14
    15             try {
    16                 // 從 settersMap 得到有多少組測試數據“ inputsNumber ”
    17                 int inputsNumber = settersMap.getInputsNumber();
    18                 // 對每組測試數據進行測試
    19                 for (int i = 0; i < inputsNumber; i++) {
    20                 // 把測試數據“ set ”到 Mock 對象中
    21                 setInput(i);
    22                 // 獲取實際 Rating 值
    23                 int rating = ((IRateable) testObject).getRating();
    24                 // 比較實際 Rating 值和期望 Rating 值是否相等,得到測試結果
    25                 assertEquals("Rating of '" + getSimpleTestObjectClassName()
    26                     + "'", settersMap.getExpectedResult(i), rating + "");
    27
    28                 AttributeLogger.getLogger().info("Rating of '"
    29                     + getSimpleTestObjectClassName() + "': "
    30                     + rating+"(actual)/"+settersMap.getExpectedResult(i)+"(expected).");
    31                 }
    32             } catch (AdvisorException ae) {
    33                 // TODO: find the handle method.
    34                 ae.printStackTrace();
    35             }
    36         }
    37
    38     }
    39
    40 }
     編寫 TestSuite 類

      由于我們構造了自己的 TestCase, TestSuite 常用的組織 TestCase 的方法需要做一點小小的改動。在我們的 TestSuite 中,提供了一個方法“ addTestCase ”。這個方法可以將 TestCase 添加到 TestSuite 中。參見清單 6。

      清單 6. addTestCase method

    1 protected void addTestCase(IRateable testObject){
    2     Test tp = null;
    3     try{
    4         // 獲得 SettersMapFactory
    5         Class factoryClass = Class.forName(factory);
    6         SettersMapFactory settersMapFactory=
    7             (SettersMapFactory)factoryClass.newInstance();
    8             
    9         // 從 SettersMapFactory 獲得 SettersMap
    10         File file = null;
    11         file = getSetterResourceFile(testObject,settersMapFactory);
    12         SettersMap settersMap = settersMapFactory.createSettersMap(file);
    13             
    14         // 創建 TestCase
    15         tp = new AttributeRatingTestCase(testObject,settersMap);
    16     }catch (Exception e){
    17         e.printStackTrace();
    18     }
    19     // 添加 TestCase 到 TestSuite;
    20     this.addTest(tp);        
    21 }

      有了 addTestCase 方法 , 我們就可以輕易的把 TestCase 添加到 TestSuite 中了。參見清單 7。

      清單 7. LWIAttributesRatingTestSuite.java

    1 public  LWIAttributesRatingTestSuite() {
    2     // 獲取 Logger.
    3     AttributeLogger.getLogger();
    4         
    5     // 獲取 SettersMapFactory 的名字
    6     Properties confProps = new Properties();
    7     try{
    8         confProps.load(new FileInputStream(CONFIG_FILE));
    9         factory = confProps.getProperty("SettersFactory");              
    10     }catch(Exception e){
    11         e.printStackTrace();
    12     }
    13         
    14     // 添加 TestCase
    15     System.out.println("LWIAttributesRatingTestSuite...");
    16     addTestCase(new MockLWITracing());
    17     addTestCase(new MockInitialHeapSize());
    18     addTestCase(new PoolPreparedStatements(null));
    19     addTestCase(new PoolMaxConnections(null));
    20     addTestCase(new MaxOpenPreparedStatements(null));
    21         
    22 }
     如果你有很多的 TestSuite, 你應該把他們很好的組織起來。在我們的測試框架中, 一個 TestSuite 在其實例化階段添加所有的 TestCase 。這就意味著我們只要擁有一個 TestSuite 的實例,我們就擁有了它所包含的 TestCase 。這樣 , 一個 AllTest 類可以以如下方式來編寫 :

      清單 8. AllTest.java

    1 public class AllTest extends TestSuite{
    2     
    3     public AllTest(){
    4         this.addTest(new LWIAttributesRatingTestSuite());
    5         this.addTest(new LWIAttributesRecommendationTestSuite());
    6     }
    7     
    8     public static void main(String[] args) {
    9         AllTest at = new AllTest();
    10         junit.textui.TestRunner.run(at);
    11     }
    12 }

      測試用例的組織可以用下圖來說明。圖中,每一個矩形都代表了一個“TestSuite”類。“TestSuite ”類以樹形結構組織起來。你可以調用任何一個類的“main”方法來執行以這個類為樹根的子樹下的所有測試用例。以“WASAllTest”類為例,執行它的“main”方法將測試 “WASRecTestSuite”和 “WASRatingTestSuite”中的所有測試用例。

      圖 3. 組織測試用例

      總結

      本文介紹了在對規則進行單元測試時實現可配置性和復用性。我們也介紹了一些常用的單元測試技術,比如使用 Mock 對象和擴展 JUnit 。這些技術可以使用到任何其他的單元測試中。

           代碼下載地址

    posted on 2009-03-06 10:04 rogerfan 閱讀(390) 評論(0)  編輯  收藏 所屬分類: 【Java知識】 、【開源技術】
    主站蜘蛛池模板: 国产色爽免费视频| 亚洲AV无码欧洲AV无码网站| 国产亚洲精品欧洲在线观看| 国产成人亚洲精品91专区手机| 国产无遮挡无码视频免费软件| 亚洲一级片在线观看| 亚洲国产综合精品一区在线播放| 久久大香香蕉国产免费网站| 亚洲欧美日本韩国| 精品亚洲综合久久中文字幕| 男人的好免费观看在线视频| 9久热精品免费观看视频| 亚洲国产精品一区二区久| 亚洲精品无码av天堂| 真人做A免费观看| 国产精品无码永久免费888| 亚洲春色另类小说| 亚洲精品国产va在线观看蜜芽| 免费视频成人片在线观看| 美女视频黄.免费网址| 亚洲高清资源在线观看| 亚洲精品美女久久久久99小说| 91香蕉在线观看免费高清| 日本一区二区三区在线视频观看免费 | 四虎影库久免费视频| 99爱视频99爱在线观看免费| 特级做a爰片毛片免费看| 亚洲人配人种jizz| 亚洲免费视频在线观看| 亚洲精品视频在线观看你懂的| 色婷婷7777免费视频在线观看| 毛片在线全部免费观看| 一级特级aaaa毛片免费观看 | 亚洲性日韩精品一区二区三区| 成人午夜免费福利视频| 久久久国产精品福利免费| 十八禁的黄污污免费网站| 亚洲熟妇成人精品一区| 亚洲成a人片在线观看中文!!!| 亚洲av无码乱码国产精品| 亚洲无码黄色网址|