程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java ME下的單元測試開發之JMUnit篇

Java ME下的單元測試開發之JMUnit篇

編輯:關於JAVA

摘要 不能因為Java Micro Edition缺乏反射能力就說Java Micro Edition開發者無法利用JUnit風格測試的優點。其實,借助於具有JUnit風格的其它一些框架和工具,Java ME開發人員仍然能夠改進Java ME應用程序的開發質量。本系列文章(兩篇)正是想詳細探討J2MEUnit和JMUnit這兩個開源框架在Java ME單元測試開發中的應用。

一、引言

如今,JUnit測試正在逐漸成為大多數Java標准版(SE)和企業版(EE)應用程序開發中的基本組成部分-對於那些積極擁護測試驅動開發者尤其如此。Kent Beck和Eric Gamma的最初的Smalltalk框架變得如此流行和成功,以至於它被移植到很多編程語言中,包括Ada(AUnit)、C#(NUnit)、Python(PyUnit),甚至還有Fortran(fUnit)。實踐證明,Java的JUnit是所有的單元測試框架中最成功的並且已經派生出許多JUnit"變種"(以各種"擴展"的形式),這些框架最終幫助在從多線程Java應用程序到高級企業Java應用程序的主流開發中實現單元測試。

然而,使用JUnit或找到一種JUnit擴展用於Java Micro Edition開發一直以來卻是很難的事情。須知,JUnit框架依賴於Java反射API。由於Java ME環境中還不支持反射API,所以,典型的很大程度上依賴於反射的JUnit工具還不能幫助進行Java ME開發。盡管如此,已經出現兩個專門針對設備應用程序開發者構建的Java ME JUnit擴展。值得注意的是,隨著NetBeans和NetBeans Mobility 5.5的發行,NetBeans和NetBeans Mobility Pack正在計劃合並一個Java ME JUnit風格的框架。這種新版本的IDE將以一種更為利索的方式在你的Java ME應用程序中加入單元測試。

本文將通過使用Java ME JUnit框架向你介紹JUnit測試;通過本文,你會發現,如何獲取這些工具,如何使用它們進行測試以及如何使用它們來構建質量更好的軟件。

二、獲取Java ME單元測試框架

如今,市場上存在兩個可用的JavaME JUnit測試框架,它們是J2MEUnit和JMUnit。這兩個工程都是可自由下載的開源框架,你可以從SourceForge.net上下載一個打包文件。

然而,這兩個開源工程的工程主管(Elmar Sonnenschein和Brunno Silva,分別維護J2MEUnit和JMUnit),正在計劃把這兩個框架合並為一個。新的工程將在J2MEUnit工程基礎上得到進一步鞏固。根據Sonnenschein本人的說法,"因為更多的現有用戶的工程都是基於SourceForge上的J2MEUnit框架;所以,我們計劃基於Brunno的JMUnit 2.0創建一個J2MEUnit 2.0發行版本。"Silva在一次最近的新聞發布會上聲稱在今年年底可能無法完成產品的合並和一個2.0版本的發行。Silva進一步建議說,新的工程"不想破壞這兩個框架的當前用戶的原有代碼,因此,原始代碼會繼續存在,只是不再建議使用。新的單元框架應該展現出兩個框架-JMUnit和J2MEUnit-各自的特色。"

三、一個簡單的示例應用程序

在分析各種單元測試框架之前,你需要一些簡單的代碼以備測試之用。在這個例子中,下面這個簡單的Conversion類能夠用於創建並測試Java ME單元測試。

public class DistanceConversion {
 public static int feetToMeters(int ft){
  return (ft * 3048)/10000;
 }
 public static int metersToFeet(int meters){
  return (meters*3281)/1000;
 }
 public static int milesToKM(int miles){
  return (miles*1609)/1000;
 }
 public static int kmToMiles(int km){
  return (km*6214)/10000;
 }
}
public class TemperatureConversion {
 public static float fahrenheitToCelsius (float degrees){
  return ((degrees-32)/9)*5;
 }
 public static float celsiusToFahrenheit (float degrees){
  return ((degrees * 9)/5)+32;
 }
 public static boolean isHotter (float degFaren, float degCel){
  return ((fahrenheitToCelsius(degFaren))-degCel) > 0;
 }
 public static boolean isCooler (float degFaren, float degCel){
  return ((fahrenheitToCelsius(degFaren))-degCel) < 0;
 }
}

注意,這段代碼中使用了CLDC 1.1原始的浮點原型。為了使該代碼能夠運行於CLDC 1.0環境下,該代碼需要使用整數原型來取代浮點原型,如下所示。另外,本文提供的下載zip源文件中也提供了一個針對CLDC 1.1和CLDC1.0的所有的這些代碼和測試類的副本。

public class DistanceConversion {
 public static int feetToMeters(int ft){
  return (ft * 3048)/10000;
 }
 public static int metersToFeet(int meters){
  return (meters*3281)/1000;
 }
 public static int milesToKM(int miles){
  return (miles*1609)/1000;
 }
 public static int kmToMiles(int km){
  return (km*6214)/10000;
 }
}
public class TemperatureConversion {
 public static int fahrenheitToCelsius (int degrees){
  return ((degrees-32)/9)*5;
 }
 public static int celsiusToFahrenheit (int degrees){
  return ((degrees * 9)/5)+32;
 }
 public static boolean isHotter (int degFaren, int degCel){
  return ((fahrenheitToCelsius(degFaren))-degCel) > 0;
 }
 public static boolean isCooler (int degFaren, int degCel){
  return ((fahrenheitToCelsius(degFaren))-degCel) < 0;
 }
}

四、使用JMUnit

a) 建立JMUnit

在下載JMUnit後,請確保相應的兩個JMUnit .jar文件(JMUnit4CLDC10.jar和JMUnit4CLDC11.jar)可用於classpath中。注意,這個參數既針對你的Java ME編譯器也針對運行時刻環境或IDE。當前,JMUnit的發行版本是1.0.2。

b) JMUnit測試用例

JMUnit提供了兩個版本的框架(每個版本都位於各自的JAR內);一個用於CLDC 1.0應用程序,另一個用於CLDC 1.1應用程序(其中,支持浮點原型)。按照典型的JUnit慣例,使用JMUnit創建適當的單元測試的第一步是創建一個測試用例。為了在JMUnit中創建一個測試用例,你必須創建一個新的派生自JMUnit的jmunit.framework.cldc10.TestCase或jmunit.framework.cldc11.TestCase的測試用例類。正如其包名所暗示的,一個支持1.0版本的CLDC,另一個支持1.1版本的CLDC。唯一的區別是,在assertEquals()和assertNotEquals()方法(見下面)的cldc11.TestCase實現中支持Java浮點原型。

按照JUnit習慣,一個測試用例類應該包含要測試的類名,並且以"Test"結束。因此,一個測試上面這個溫度轉換類的簡單的CLDC 1.1版本的JMUnit測試用例可以按如下方式定義:

public class TemperatureConversionTest extends jmunit.framework.cldc11.TestCase {}

所有的測試方法必須位於一個測試用例類之內。而且,按照慣例,測試方法名都以"test"開頭,然後根據被測試的類中的方法進行命名。例如,一個測試fahrenheitToCelsius方法的測試用例方法應該為testfahrenheitToCelsius。每一個測試方法必須"斷言"期望的結果。對於那些不熟悉JUnit測試的開發者來說,一個斷言其實就是一個語句,它負責驗證或證明從某個方法執行中程序員所期望的結果。JMUnit支持下列斷言:

assertTrue(expression)
assertFalse(expression)
assertSame(expected,actual)
assertNotSame(expected,actual)
assertEquals(expected,actual)
assertNotEquals(expected,actual)
assertNull(object)
assertNotNull(object)

在JMUnit中,任何使用這些斷言調用之一的測試方法都必須拋出一個AssertionFailedException異常。框架使用該異常來標識失敗的測試。現在,這個添加了適當測試方法的TemperatureConversionTest類看起來如下所示。

import jmunit.framework.cldc11.*;
public class TemperatureConversionTest extends TestCase {
 public void testfahrenheitToCelsius() throws AssertionFailedException{
  System.out.println("fahrenheitToCelsius");
  float result = TemperatureConversion.fahrenheitToCelsius(66F);
  assertEquals(18.88889F,result);
 }
 public void testcelsiusToFahrenheit() throws AssertionFailedException{
  System.out.println("celsiusToFahrenheit");
  float result = TemperatureConversion.celsiusToFahrenheit(20F);
  assertEquals(68F, result);
 }
 public void testisHotter() throws AssertionFailedException {
  System.out.println("isHotter");
  assertTrue(TemperatureConversion.isHotter(70F,2F));
 }
 public void testisCooler() throws AssertionFailedException {
  System.out.println("isCooler");
  assertTrue(TemperatureConversion.isCooler(10F,10F));
 }
}

對於每一個標准的JUnit實現,JMUnit測試用例抽象類都提供了setup()和tearDown()方法,這兩個方法都能夠被重載並用於初始化,並在經由測試用例運行測試前後用來清除任何對象或資源。例如,在Java ME應用程序中,setup()可以用於在測試前打開一個記錄存儲,而tearDown()用於在測試後關閉記錄存儲。除了setup和tearDown方法外,還有一個fail()方法用於實現-無論assert語句顯示什麼內容,都允許一個測試方法返回一個測試失敗。這個方法經常用於一個測試方法內的某些條件中,或用於作為未開發的單元測試的一個代理,從而作為一種方式來指示尚待完成的工作。

JMUnit中的每一個測試用例類都有一個相應的構造器。因此,派生自JMUnit的測試用例類的構造器必須調用超類構造器,並傳入一個整數以指示在該測試用例中的測試個數,還要傳入一個字符串來標識該測試用例。

public TemperatureConversionTest() {
 super(4,"TemperatureConversionTest");
}

這個整數指示測試的個數必須匹配測試用例中的實際測試的數目。確保你傳入構造器的測試的個數匹配測試用例中的實際的測試的個數是相當重要的。當你分析該測試用例的test(int testNumber)方法,就會看到它們之間的關系。

測試用例中的這個test(int testNumber)方法負責"剔除"測試方法。因為Java ME缺乏映射能力,所以不能象在JUnit中一樣,找到test方法並自動地執行之。因此,每一個測試方法必須被添加到該test方法中的一個switch語句中,並且基於一個測試號進行相應的調用。在我們的TemperatureConversionTest情況下,這個test方法看起來如下列代碼所示:

public void test(int testNumber) throws Throwable {
 switch(testNumber) {
  case 0:testfahrenheitToCelsius();break;
  case 1:testcelsiusToFahrenheit();break;
  case 2:testisHotter();break;
  case 3:testisCooler();break;
  default: break;
 }
}

這也正解釋了為什麼你必須向測試用例構造器提供一個測試號。在運行時刻,JMUnit框架創建一個測試用例類的實例。然後,框架在一個循環內調用該測試用例實例的每一個測試方法。通過這種方式,測試方法的switch語句中的每一個case語句(以及相應的每一個測試)都會被框架所調用。當把一個測試方法添加到測試用例中時,如果忘記更新測試用例類的構造器可能會導致部分測試用例不被激活。

因為你使用JMUnit編寫測試方法,那麼這可能會比基於JUnit更靈活一些:它允許執行測試方法。借助於測試方法的控制作用,你可以編寫使用參數的測試-而這一點JUnit是不允許(由於反射機制)的。例如,針對TemperatureConverstionTest方法的一個測試方法可能看起來如下所示:

public void testcelsiusToFahrenheit(float c, float f) throws AssertionFailedException{
 System.out.println("celsiusToFahrenheit(float c)");
 float result = TemperatureConversion.celsiusToFahrenheit(c);
 assertEquals(f, result);
}

然後,該測試方法就可以使用參數來調用switch語句中的這個測試方法。

public void test(int testNumber) throws Throwable {
 switch(testNumber) {
  case 0:testfahrenheitToCelsius();break;
  case 1:testcelsiusToFahrenheit();break;
  case 2:testisHotter();break;
  case 3:testisCooler();break;
  case 4:testcelsiusToFahrenheit(20F,68F);break;
  default: break;
 }
}

c) JMUnit測試集

測試集負責管理一個或多個測試用例。JMUnit提供了兩個測試集抽象類(jmunit.framework.cldc10.TestSuite和jmunit.framework.cldc11.TestSuite),你可以從它們進行繼承以便創建一個測試集。就象測試用例一樣,你應該繼承的測試集的類型依賴於你在使用哪一個版本的CLDC。cldc10.TestSuit適用於CLDC 1.0應用程序,而cldc11.TestSuite適用於CLDC 1.1應用程序。這兩個測試集抽象類都分別提供了一個以一個字符串作為參數的構造器。該字符串用於給出測試集的一個描述。

一個測試集的唯一功能是創建它的所有測試用例的一個實例,然後調用這些測試用例的測試方法。為了在一個測試集上添加一個測試用例,在構建測試集時應該添加add(testCase)方法。下面是一個實現轉換測試用例的測試集的例子:

import jmunit.framework.cldc11.TestSuite;
public class ConversionTestSuite extends TestSuite{
 public ConversionTestSuite() {
  super("All Conversion Tests");
  add(new DistanceConversionTest());
  add(new TemperatureConversionTest());
}

d) 執行JMUnit測試

JMUnit的TestCase和TestSuite抽象類都是MIDlet的子類。這允許你在一個仿真器(也有可能是一個真實設備)中運行你的單個測試用例或測試集。當在一個模擬器上運行時,每一個測試用例或測試集都提供兩個命令:exit和test。圖1描述了上面描述的測試集相應的執行結果;圖2展示了失敗時顯示的內容。

圖1.執行一個測試用例:執行一個JMUnit測試集使你能夠選擇退出或測試該測試集。測試集的結果以圖形方式顯示。

圖2.一個失敗測試用例:當一個測試用例失敗時,失敗情況以紅色圖形方式顯示。

圖3.失敗測試用例的控制台輸出:失敗時的文本輸出指出哪個測試用例失敗了,為什麼它失敗,並且提供一個堆棧跟蹤結果以幫助確定它在哪兒失敗的。

因此,當執行測試時,你還要檢查該控制台(見圖3)。失敗信息通過控制台以更好的文檔形式輸出。這些失敗輸出包括堆棧跟蹤信息,還有來自於該測試的實際的和期望的值。比較於隨後我們將討論的J2MEUnit,這可能是JMUnit所缺乏的特征之一。在J2MEUnit中,不是使用控制台輸出,測試用例失敗情形將被顯示到模擬設備上。

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