引言
本文面向的是那些想要對他們的 EJB 進行單元測試以及為這些 EJB 開發測試案例的 VisualAge® for Java™ 用戶。本文基於 VisualAge for Java 3.5.3 和 JUnit 3.7。文章描述了 JUnit、對 EJB 進行單元測試的難點以及開發測試案例時涉及到的相關步驟。
單元測試是以程序員的視角來編寫的。單元測試確保一個類的某個特定的方法能成功地執行一組特定的任務。每個測試確定一個方法在給定已知的輸入時能產生預期的輸出。有效測試是有效編程的一個基本的組成部分。通過使用 JUnit 測試框架,您能容易地並且逐步地構建一個測試套件,這個測試套件能幫助您調節工作進度、發現不希望出現的副作用並把精力集中在開發工作上。
編寫 EJBs 測試案例
這裡是一個關於 EJB 的示例,該 EJB 帶有一個名為 addition 的業務方法,該方法以兩個整型變量作為輸入,將它們相加後返回結果:
/**
* This is a Session Bean Class
*/
public class SampleEjbBean implements SessionBean {
private javax.ejb.SessionContext mySessionCtx = null;
final static long serialVersionUID = 3206093459760846163L;
/**
* Insert the method's description here.
* Creation date: (8/10/02 1:16:33 PM)
* @return int
* @param a int
*/
//The Business method
public int addition(int a,int b) {
return a+b;
}
public void ejbActivate() throws java.rmi.RemoteException {...}
public void ejbCreate() throws javax.ejb.CreateException, java.rmi.RemoteException {...}public void ejbPassivate() throws java.rmi.RemoteException {...}
public void ejbRemove() throws java.rmi.RemoteException {...}
public javax.ejb.SessionContext getSessionContext() {
.....
}
public void setSessionContext(javax.ejb.SessionContext ctx) throws java.rmi.RemoteException {
.....
}
}
使用下列步驟創建一個 EJB 測試案例。
通過繼承 JUnit.framework.TestCase 類創建一個測試類。命名約定:如果 bean 的名稱是 SampleEjbBean ,則將測試類命名為 SampleEjbBeanTest 。例如:
public class SampleEjbBeanTest extends JUnit.framework.TestCase{ 。
創建 Bean 的一個 remoteInterface 類型的類變量。例如:
SampleEjb remoteInterface
創建測試類的一個靜態實例:static {
instance = new SampleEjbBeanTest("");
}
因為該實例被用來作為 TestRunner 的 run 方法的一個參數以執行 TestClass.main 方法和測試案例,所以您可以在 SwingUI 或者 TextUI 中執行測試案例:
public static void main(String args[])
{
if (args.length > 0){
if (args[0].equals("SWING")) {
JUnit .swingui.TestRunner.run(instance.getClass());
}
else {
JUnit .textui.TestRunner.run(instance.getClass());
}
}
else {
//formatting the Output
System.out.println("************************************");
String className = instance.getClass().getName();
className = className.substring(className.lastIndexOf(".")+1);
System.out.println("Test Report of:"+className);
System.out.println("************************************");
JUnit.textui.TestRunner.run(instance.getClass());
}
}
接著,創建一個用於連接運行在服務器上的 EJB bean 的方法並為遠程接口創建句柄:
將初始上下文添加到 HashMap 中。例如:
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.ibm.ejs.ns.jndi.CNInitialContextFactory
將 URL 添加到 HashMap 中。例如:
env.put(Context.PROVIDER_URL, "iiop://localhost:900");
創建 InitialContext 對象。例如:
javax.naming.InitialContext ic =new javax.naming.InitialContext(env);
通過在命名服務器中查找 EJB Alias 名稱來構造 Bean 的一個 homeInterface 例如:
SampleEjbHome homeInterface = (SampleEjbHome) ic.lookup("SampleEjb");
通過調用 homeInterface 的 create 方法創建一個 remoteInterface 。 例如:
remoteInterface = homeInterface.create();Public void getConnection()
{
getinfo("Running " + this.toString());
java.util.Hashtable env = new Hashtable();
//Adding the Initial Context to the HashMap.
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.ibm.ejs.ns.jndi.CNInitialContextFactory
");
env.put(Context.PROVIDER_URL, "iiop://localhost:900");
try
{
getinfo("Creating an initial context");
javax.naming.InitialContext ic =new javax.naming.InitialContext(env);
getinfo("Looking for the EJB " + "SampleEjb");
SampleEjbHome homeInterface =
(SampleEjbHome) ic.lookup("SampleEjb");
getinfo("Creating a new EJB instance");
remoteInterface = homeInterface.create();
}
catch (NamingException e)
{
getinfo(e.toString());
fail();
}
catch (Exception e)
{
getinfo("Create Exception");
getinfo(e.toString());
fail();
}
}
在用於建立固定設備(fixture)(例如,打開一個網絡連接,打開一個文件)的 setup 方法中,這個方法在測試程序執行前被調用。調用 getConnection() 方法使得測試案例連同 EJB 設置一起得以初始化。
public void setUp() throws Exception
{
......
/*Invoking the getConnection method*/
getConnection();
}
為方法創建測試案例。使用下列命名約定:將測試案例命名為 testXXXX(testAddition) 。在 testMethod(testAddition) 中,從 remoteInterface 對象調用業務方法( addition )。例如:int result=remoteInterface.addition(10,15);
public void testAddition() {
try{
int result=remoteInterface.addition(10,15);
getinfo("----------------------------------");
getinfo("Success without Exception");
assertEquals(25,result);
}
catch(Exception e){
fail("Fail"+e);
System.out.println("Error"+e);
}
}
為報告而開發的其他方法:
public void getinfo(boolean msgObj)
{
System.out.println(msgObj);
}
public void getinfo(String msg)
{
System.out.println(msg);
}
執行測試案例
部署 EJB。
在 VisualAge for Java 上啟動 WebSphere 測試環境(WebSphere Test Environment)和持久名稱服務器(Persistence Name Server)。
將 EJB 添加到服務器配置中。
啟動 EJB 服務器。
在 VisualAge for Java 中調用 main 方法執行測試類。以下為運行結果:
************************************
Test Report of: SampleEjbBeanTest
************************************
. Running testAddition(com.prudential.retserv.chart.servlet.SampleEjbBeanTest)
Creating an initial context
Looking for the EJB SampleEjb
Creating a new EJB instance
----------------------------------
Success without exception
Time: 15.984的
OK (1 tests)
通過此方法,您能開發測試案例並從 EJB 被部署的地方對該 EJB 進行單元測試。
對 EJB 進行單元測試時的問題
單元測試在單獨地、隔離地並快速地運行時,運行狀況最佳。一個測試案例通常會構造它正在測試的對象。然而,有時這個正在被測試對象要依賴於其他對象的行為。在這些情況下,測試案例會建立一個測試工具以“模擬”所需的行為。這個測試工具與真實系統有同樣的接口,但實際上卻不做任何事,它“欺騙”被測試的單元使它相信是系統在測試它。EJBs 通常依賴於它們的容器所提供的上下文,並且如果沒有那些上下文,大多數的 EJBs 將不會起作用。測試案例必須從執行該 EJB 的容器中調用 EJB。您也可以在 EJB 對象所在的本地環境中測試它們,但這樣做可能使測試的正確性大打折扣。所以,所開發的測試案例應該在部署了 EJB 並且正在執行 EJB 的容器中調用 EJB。
關於 JUnit 的附加信息
JUnit 測試能讓您更快地開發出質量更高的代碼。
JUnit 測試檢查它們自身的運行結果並提供及時的反饋。
JUnit 測試能被組織成呈層次結構的多個測試套件。
您能快捷地並廉價地編寫 JUnit 測試案例。
開發人員能很容易地完成 JUnit 測試。
JUnit 測試是用 Java 寫成的。
JUnit 能測試 EJBs,servlets 和線程程序。
JUnit 是圍繞兩種主要的設計模式而設計的:命令模式和復合模式。
JUnit 是免費的。
JUnit 不具備的功能
JUnit 不會從測試代碼中分離出測試數據。
JUnit 不能對 Swing GUIs, JSPs 或者 HTML 進行單元測試。
JUnit 測試不能取代功能測試。即使您所有的單元測試都成功,也並 不意味著你的軟件一切就緒。
JUnit 不能單元測試服務器端的 Java 代碼。幾種 Xunit 測試框架被設計用來單元測試服務器端的 Java 代碼。
結束語
單元測試非常重要。它們不僅能減少開發時間更能提高代碼質量。在 EJB 代碼被開發前首先將單元測試寫成一個套件,這些單元測試將提供一個強有力的確認與回歸工具。JUnit 能使單元測試案例的編寫工作變得更輕松。