程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 追求代碼質量 - 用JUnitPerf進行性能測試

追求代碼質量 - 用JUnitPerf進行性能測試

編輯:關於JAVA

在應用程序的開發中,驗證應用程序的性能幾乎總處於次要的地位。請注意 ,我強調的是驗證 應用程序的性能。應用程序的性能總是 首要考慮的因素,但 開發周期中卻很少包含對性能的驗證。

由於種種原因,性能測試常被延遲到開發周期的後期。以我的經驗,企業之 所以在開發過程中不包含性能測試是因為,他們不知道對於正在進行開發的應用 程序要期待什麼。提出了一些(性能)指數,但這些指數是基於預期負載提出的 。

發生下列兩種情況之一時,性能測試就成為頭等大事:

生產中出現顯而易見的性能問題。

在同意付費之前 ,客戶或潛在客戶詢問有關性能指數的問題。

本月,我將介紹兩種簡單的性能測試技術,在上述兩種情況中的任何一種發 生前進行測試。

用 JUnitPerf 進行測試

在軟件開發的早期階段,使用 JUnit 很容易確定基本的低端性能指數。JUnitPerf 框架能夠將測試快速地轉化為簡單的負載測試,甚至壓力測試。

可使用 JUnitPerf 創建兩種測試類型:TimedTest 和 LoadTest。這兩種類 型都基於 Decorator 設計模式並利用 JUnit 的 suite 機制。TimedTest 為測 試樣例創建一個(時間)上限 —— 如果超過這個時間,那麼測試失敗。 LoadTest 和計時器一起運行,它通過運行所需的次數(時間間隔由配置的計時 器控制),在一個特定的測試用例上創建一個人工負載。

恰當的時限測試

JUnitPerf TimedTest 讓您可以編寫有相關時間限制的測試 —— 如果超過 了該限度,就認為測試是失敗的(即便測試邏輯本身實際上是成功的)。在測試 對於業務致關重要的方法時,時限測試相比其他測試來說,在確定和監控性能指 數方面很有幫助。甚至可以測試得更加細致一些,可以測試一系列方法來確保它 們滿足特定的時間限制。

例如,假設存在一個 Widget 應用程序,其中,特定的對於業務致關重要的 方法(如 createWidget())是嚴格的性能限制的測試目標。假設需要對執行該 create() 方法的功能方面進行性能測試。這通常會由不同的團隊使用不同的工 具在開發周期的後期加以確定,這通常不能指出精確的方法。但假設決定選擇早 期經常測試 方法取而代之。

創建 TimedTest 首先要創建一個標准的 JUnit 測試。換言之,將對 TestCase 或其派生類進行擴展,並編寫一個以 test 開頭的方法,如清單 1 所 示:

清單 1. 簡單的 widget 測試

public class WidgetDAOImplTest  extends TestCase {
  private WidgetDAO dao;

  public void testCreate() throws Exception{
  IWidget wdgt = new Widget();
  wdgt.setWidgetId(1000);
  wdgt.setPartNumber("12-34-BBD");
  try{
   this.dao.createWidget(wdgt);
  }catch(CreateException e){
   TestCase.fail("CreateException thrown creating a  Widget");
  }
  }

  protected void setUp() throws Exception {
  ApplicationContext context =
   new ClassPathXmlApplicationContext("spring-config.xml");
  this.dao = (WidgetDAO) context.getBean("widgetDAO");
  }
}

由於 JUnitPerf 是一個基於裝飾器的框架,為了真正地駕馭它,必須提供一 個 suite() 方法並將現有的測試裝飾以 TimedTest。TimedTest 以 Test 和執 行該測試的最大時間量作為參數。

也可以選擇傳入一個 boolean 標志作為第三個參數(false),這將導致測 試快速失敗 —— 意味著如果超過最大時間,JUnitPerf 將立即 迫使測試失敗 。否則,測試樣例將完整運行,然後失敗。區別很微妙:在一個失敗的樣例中, 不帶可選標志運行測試可以幫您了解運行總時間。傳入 false 值卻意味著得不 到運行總時間。

例如,在清單 2 中,我在運行 testCreate() 時設定了一個兩秒鐘的上限。 如果執行總時間超過了這個時間,測試樣例將失敗。由於我並未傳入可選的 boolean 參數,該測試將完整運行,而不管運行會持續多久。

清單 2. 為生成 TimedTest 而實現的 suite 方法

public static  Test suite() {
  long maxElapsedTime = 2000; //2 seconds
  Test timedTest = new TimedTest(
   new WidgetDAOImplTest("testCreate"), maxElapsedTime);
  return timedTest;
}

此測試通常在 JUnit 框架中運行 —— 現有的 Ant 任務、Eclipse 運行器 等等,會像運行任何其他 JUnit 測試一樣運行這個測試。惟一的不同是,該測 試將發生在計時器的上下文中。

過度的負載測試

與在測試場景中驗證一個方法(或系列方法)的時間限制正好相反, JUnitPerf 也方便了負載測試。正如在 TimedTest 中一樣,JUnitPerf 的 LoadTest 也像裝飾器一樣運行,它通過將 JUnit Test 和額外的線程信息綁定 起來,從而模擬負載。

使用 LoadTest,可以指定要模擬的用戶(線程)數量,甚至為這些線程的啟 動提供計時機制。JUnitPerf 提供兩類 Timer:ConstantTimer 和 RandomTimer 。通過為 LoadTest 提供這兩類計時器,可以更真實地模擬用戶負載。如果沒有 Timer,所有線程都會同時啟動。

清單 3 是用 ConstantTimer 實現的含 10 個模擬用戶的負載測試:

清單 3. 為生成負載測試而實現的 suite 方法

public static Test  suite() {
 int users = 10;
 Timer timer = new  ConstantTimer(100);
 return new LoadTest(
 new  WidgetDAOImplTest("testCreate"),
  users, timer);
}

請注意,testCreate() 方法運行 10 次,每個線程間隔 100 毫 秒啟動。未設定時間限制 —— 這些方法完整運行,如果其中任何的 方法執行失敗,JUnit 會相應地報告失敗。

用樣式進行裝飾

裝飾 器並不局限於單個的裝飾物。例如,在 Java™ I/O 中,可以為 FileInputStream 裝飾上一個帶 BufferedReader 的 InputStreamReader(只要 記住:BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("infilename"), "UTF8")))。

裝飾可以有多個層次,JUnitPerf 的 TimedTest 和 LoadTest 也是一樣。當 這兩個類彼此裝飾時,將導致一些強制的測試場景,例如像這樣的場景:在一項 業務中放置了負載並應用了時間限制。或者,我們可以僅僅將之前的兩個測試場 景以如下方式結合起來:

在 testCreate() 方法中放置一項負載。

規定每個線程必須在該時間限制內結束。

我通過為一個標准 Test 裝飾上 LoadTest(由 TimedTest 裝飾)應用了上述規范,清單 4 顯示了 其結果。

清單 4. 經裝飾的負載和時限測試

public static  Test suite() {
 int users = 10;
 Timer timer = new  ConstantTimer(100);
 long maxElapsedTime = 2000;
  return new TimedTest(new LoadTest(
  new WidgetDAOImplTest ("testCreate"), users, timer),
   maxElapsedTime);
}

正如您所看到的那樣,testCreate() 方法運行 10 次(每隔 100 毫秒啟動 一個線程),且每個線程必須在 2 秒內完成,否則整個測試場景將失敗。

使用注意

盡管 JUnitPerf 是一個性能測試框架,但也要先大致估計一下測試要設定的 性能指數。這是由於所有由 JUnitPerf 裝飾的測試都通過 JUnit 框架運行,所 以就存在額外的消耗,特別是在利用 fixture 時。由於 JUnit 本身用一個 setUp 和一個 tearDown() 方法裝飾所有測試樣例,所以要在測試場景的整個上 下文中考慮執行時間。

相應地,我經常創建使用我想要的 fixture 邏輯的測試,但也會運行一個空 白測試來確定性能指數基線。這是一個大致的估計,但它必須作為基線添加到任 何想要的測試限制中。

例如,如果運行一個由 fixture 邏輯(使用 DbUnit)裝飾的空白測試用時 2.5 秒,那麼您想要的所有測試限制都應將這一額外時間考慮在內 —— 這可以 從清單 5 中的基准測試中看到:

清單 5. JUnitPerf 基准測試

public class  DBUnitSetUpBenchmarkTest extends DatabaseTestCase {
  private WidgetDAO dao = null;

  public void testNothing(){
  //should be about 2.5 seconds
  }

  protected IDatabaseConnection getConnection() throws  Exception {
  Class driverClass = Class.forName ("org.hsqldb.jdbcDriver");
  Connection jdbcConnection =
   DriverManager.getConnection(
    "jdbc:hsqldb:hsql://127.0.0.1", "sa", "");
  return new DatabaseConnection(jdbcConnection);
  }

  protected IDataSet getDataSet() throws Exception {
  return new FlatXmlDataSet(new File("test/conf/seed.xml"));
  }

  protected void setUp() throws Exception {
  super.setUp();
  final ApplicationContext context =
   new ClassPathXmlApplicationContext("spring-config.xml");
  this.dao = (WidgetDAO) context.getBean("widgetDAO");
  }
}

請注意,清單 5 的測試樣例 testNothing() 什麼都沒做。其惟一的目的是 確定運行 setUp() 方法(當然,該方法也通過 DbUnit 設置了一個數據庫)的 總時間。

也請記住,測試時間將依賴於機器的配置而變化,同時也依賴於在執行 JUnitPerf 測試時運行的東西而變化。我經常發現,將 JUnitPerf 測試放到它 們自己的分類中有助於將它們同標准測試隔離開。這意味著,在運行一個測試時 不必每次都運行 JUnitPerf 測試,例如在一個 CI 環境中簽入代碼。我也會創 建特定的 Ant 任務,從而只在精心策劃的將性能測試考慮在內的場景或環境中 運行這些測試。

試試吧!

用 JUnitPerf 進行性能測試無疑是一門嚴格的科學,但在開發生命周期的早 期,這是確定和監控應用程序代碼的低端性能的極佳方式。另外,由於它是一個 基於裝飾器的 JUnit 擴展框架,所以可以很容易地用 JUnitPerf 裝飾現有的 JUnit 測試。

想想您已經花了這麼多時間來擔心應用程序在負載下會怎樣執行。用 JUnitPerf 進行性能測試可以為您減少擔憂並節省時間,同時也確保了應用程序 代碼的質量。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved