歡迎您光臨本站 註冊首頁

用 XML-RPC 和 Abbot 來進行 Eclipse 程序的遠程測試

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
本文介紹了一種針對於 Eclipse 程序的遠程自動化測試技術。該技術結合了 XML-RPC 技術和 GUI 自動化測試庫 Abbot SWT,適用於網路協作、富客戶端以及即時通信軟體的自動化功能測試。本文首先介紹了 Abbot SWT 的簡單應用,然後介紹了遠程測試技術的架構和實現,最後通過一個部署遠程測試實例來說明該技術的應用方法。

前言

現在 Eclipse 平台被廣泛應用於各種軟體的架構中,從開發工具到測試工具,從網路協作軟體到即時通訊工具,Eclipse 平台被證明了其強大的跨平台,可擴展,可配置的優良特性。正因為如此,對 Eclipse 程序的測試也越來越流行。Abbot 是一個開源的 Java 程序的界面自動化測試庫,Abbot SWT 是其中一個子項目,專門針對於 SWT 程序的測試。但是,Abbot SWT 只能對本地的程序進行測試,當我們所需要進行的 GUI 功能測試需要多個協作軟體之間進行通信時,Abbot SWT 庫只能測試其中一個程序,而不能同時測試部署在不同機器上的基於 Eclipse 軟體。我們可以將 XML-RPC 技術同 Abbot SWT 庫相結合起來,遠程測試基於 Eclipse 的程序,這樣可以同時測試部署在不同位置的 Eclipse 程序之間的協作和通信。大大擴展了 Abbot SWT 庫的使用領域。

本文首先從 Abbot SWT 的基本用法開始,然後介紹如何結合 XML-RPC 和 Abbot 庫進行遠程的自動化測試,最後介紹一個實際的部署和遠程自動化測試的實例。





用 Abbot 進行 Eclipse 程序的 GUI 功能測試

Abbot SWT 是一個優秀的 GUI 功能測試庫。它能夠對 SWT 控制項、窗體和菜單進行功能性自動化測試。同時它又是一個 Eclipse 插件,可以直接安裝到 Eclipse 程序中來做針對界面控制項的單元測試。

我們可以在 JUnit 單元測試用例中調用 Abbot SWT 插件來進行 GUI 自動化測試。一個簡單的測試用例如清單1所示。該測試用例是用來測試一個 Text 控制項是否能夠正確輸入文本,通過獲得該控制項的當前內容來與輸入文本進行比較來判斷結果的正確與否。


清單 1 一個基於 Abbot SWT 庫的 JUnit 單元測試用例
public class TextTesterTest extends SWTTestCase {  Shell shell;  protected void setUpDisplay() {  		super.setUpDisplay();    		shell = createShell();  		shell.setLayout(null);  		Text text = new Text(shell, SWT.MULTI | SWT.WRAP);  		text.setSize(190, 190);  }  // Test case: input text and verify  public void testSetText() throws NotFoundException, MultipleFoundException {  	TextTester textTester = TextTester.getTextTester();  	WidgetFinder finder = WidgetFinderImpl.getDefault();  	  	// Set up testing environment: show the shell  	syncExec(new Runnable() {  		public void run() {  			shell.setLocation(100, 100);  			shell.open();  		}  	});  	  	sleep(1000);    	// Force it to be active and give it a moment to do so.  	syncExec(new Runnable() {  		public void run() {  			shell.forceActive();  		}  	});  	  	WidgetTester.getDefault().waitForIdle();  	  	// Find the text widget instance  	Text textwidget = (Text) finder.find(shell, new WidgetClassMatcher(Text.class));  	  	// Do the testing, click the widget, input text, verify the result text  	String string = "example text...";  	textTester.actionClick(textwidget);           textTester.actionKeyString(textwidget, string);  	assertEquals("Wrong short text typed,", string, textTester.getText(textwidget));  }  //... Other methods and test cases  

Abbot SWT 庫提供了用於不同搜索條件的匹配類( WidgetTextMatcher, WigetClassMatcher … )和用於查找控制項的查找類( WidgetFinder ),通過不同的匹配條件對象作為參數傳給控制項查找類,從而返回所要測試的控制項。代碼片段如清單2所示。


清單 2 Abbot SWT 的匹配類和查找類
	TextTester textTester = TextTester.getTextTester();  	WidgetFinder finder = WidgetFinderImpl.getDefault();  	Text textwidget = (Text) finder.find(shell, new WidgetClassMatcher(Text.class));  

通過控制項類(Text.class)和 WidgetClassMatcher 類來搜索被測控制項的對象,然後獲得該控制項對應的測試類,來進行測試。對 Text 控制項來說,對它的測試代碼如清單3所示。


清單 3 Text 控制項測試代碼
	// Do the testing, click the widget, input text, verify the result text  	String string = "example text...";  	textTester.actionClick(textwidget);  	textTester.actionKeyString(textwidget, string);  	assertEquals("Wrong short text typed,", string, textTester.getText(textwidget));  

通過 TextTeser 類的點擊測試方法 actionClick 點擊該控制項,通過 actionKeyString 來輸入測試文本,通過 getText 獲得當前顯示的文本屬性,最終驗證結果的正確性。

通過上面的例子我們可以看到,通過 Abbot SWT 提供的 Finder 類和 Matcher 類,可以獲得我們所期望的控制項的對象,通過控制項類所對應的Abbot 的 Tester 類,我們可以進行不同的測試,包括滑鼠,按鍵,快捷鍵,拖拽以及組合鍵的輸入等等。這樣我們就可以通過創建 JUnit 的用例來測試 GUI 程序中的不同控制項,驗證其正確性。





結合 XML-RPC 技術和 Abbot 來實施 Eclipse 程序的遠程測試

Abbot SWT 插件需要和被測的 Eclipse 程序處於同一個進程中,而不能跨進程測試其他程序。這就限定了該庫只能用於進行 UI 組件的單元測試中,而無法作為一個功能測試工具來測試不同進程的程序,更不用說測試分佈在網路中的不同機器上的程序。但是,我們可以通過擴展 Abbot SWT 庫,基於 Eclipse 平台和 RPC 技術,將 Abbot SWT 的介面類開放到網路中,作為網路服務提供給運行測試用例的客戶端,這樣就能夠跨進程、跨網路來進行功能測試。下面首先介紹一下該方法的總體架構,然後是其中的一些關鍵技術。

系統架構

我們選擇 XML-RPC 作為遠程測試 Eclipse 的基礎 RPC 方法。XML-RPC 作為傳統的 RPC 技術,其有著成熟的技術和庫,而且實施簡單,調試方便,非常適合簡單應用的 RPC 技術的基礎。通過在被測的 Eclipse 程序中添加一個自動化測試服務的插件,該插件開放 RPC 服務,就能夠讓客戶端調用該服務來與被測程序中的 Abbot SWT 插件進行通信,從而進行遠程的測試。其頂層架構如圖 1所示。


圖 1. 頂層架構

從該頂層架構我們可以看到,測試用例可以是一個腳本、一個程序也可以是一個 JUnit 測試用例。它通過調用 XML-RPC 服務,和被測進程中的 Abbot SWT 庫通信,通過構建自己的功能測試邏輯來最終進行遠程地自動化功能測試。一個典型的自動化測試過程如圖 2所示。


圖 2. 典型的遠程測試過程

從圖 2我們可以看到,這個自動化測試服務插件將 Abbot SWT 的類介面作為 RPC 的服務開放到網路中,將收到的 RPC 請求轉成 Abbot SWT 庫的調用。同時,由於在網路中無法傳遞控制項的對象,這裡用控制項的 ID 值來表示被測控制項。最後在測試用例進程中來判斷比較測試結果,提交報告。

下面我們詳細敘述其中的關鍵技術:控制項和 ID 之間的映射和 RPC 動態代理。

控制項 ID 映射

由於 RPC 的方式無法傳輸一個被測控制項的對象,我們用整數值來表示一個被測的控制項,這就要求對每個 SWT Widget 類的對象進行映射,將每個被測的對象和一個隨機的整型數對應起來。在上述的自動化測試服務插件中,生成這樣一張表:它將一個整型數對應到一個 UI 控制項對象,同時也能將一個 UI 控制項的對象對應到一個整型數,這樣能無論是從一個 ID 到一個控制項對象,還是從一個控制項對象到一個 ID ,都是 ο(1) 的時間複雜度。其數據結構如圖 3所示。


圖 3. 控制項映射數據結構

在具體的代碼實現中,可以通過2個 HashMap 來實現,其定義如清單4。


清單4 HashMap 實現雙向映射
public class WidgetTable {  private Map<Integer, Object> ID2Objecttable = Collections  			.synchronizedMap(new HashMap<Integer, Object>());  	private Map<Object, Integer> Object2IDtable = Collections  			.synchronizedMap(new HashMap<Object, Integer>());       	public Object lookup(int id) {  		return ID2Objecttable.get(new Integer(id));  	}  	//...  

但是,由於 SWT 控制項可有在測試過程中被銷毀,這樣在控制項映射表中也必須將銷毀的控制項刪除。所以在每次往表中註冊添加一個控制項的時候,給該控制項添加一個 Dispose 的偵聽器,這樣當該控制項被銷毀時,也相應的在映射表中刪除該條映射,如圖 4所示。


圖 4. 控制項映射數據結構的維護

實現代碼如清單5所示。


清單5 維護控制項映射的數據結構
	public int registeWidget(final Widget w) {  		// if existed, return id  		if (Object2IDtable.containsKey(w))  			return Object2IDtable.get(w);    		// add a new listener to the widget  		w.getDisplay().syncExec(new Runnable() {  			public void run() {  				w.addDisposeListener(new WidgetDisposeListener());  			}  		});    		int id = generateUniqueID();  		ID2Objecttable.put(new Integer(id), w);  		Object2IDtable.put(w, new Integer(id));  		return id;  	}  	private class WidgetDisposeListener implements DisposeListener {  		public void widgetDisposed(DisposeEvent e) {  			unregisteWidget(e.widget);  		}    	}    	private void unregisteWidget(Widget w) {  		if (Object2IDtable.containsKey(w)) {  			ID2Objecttable.remove(Object2IDtable.get(w));  			Object2IDtable.remove(w);  		} else {  			// throw new Error("Can not find the widget in Widget Table.");  			System.err.println("Can not find the widget in Widget Table.");  		}  	}  

RPC動態代理

在實現 XML-RPC 服務以及客戶端時,我們採用了 Apache XML-RPC 庫的動態代理技術,該技術只需要服務端和客戶端公用一套介面類,就能實現它們之間的 XML-RPC 通信。其公用的介面類定義如清單6所示。


清單6 RPC 介面類
// WidgetTesterProxy  Abbto SWT WidgetTester  public interface WidgetTesterProxy {  	public int actionClick(int id);  	public int actionClick(int id, int mask);  	public int actionClick(int id, int x, int y);  	public int actionClick(int id, int x, int y, int mask);  	public int actionClick(int id, int x, int y, int mask, int count);  	public int actionClick(int id, int x, int y, String buttons);  	public int actionClick(int id, int x, int y, String buttons, int count);  	public int actionDoubleClick(int id);  	public int actionDoubleClick(int id, int mask);  	public int actionKey(int accelerator);  	public int actionKeyString(String text);  	public int actionFocus(int id);    ... //other test methods…  }  // ButtonTesterProxy  Abbot SWT ButtonTester  public interface ButtonTesterProxy extends ControlTesterProxy {  	public int getAlignment(final int button);  	public boolean getSelection(final int button);  	public String getText(final int buttonORwidget);  	public boolean isTextEditable(final int widget);  }  … //other Tester Proxy interface    //Widget Finder interface  Abbot SWT Finder class  public interface WidgetFinderProxy {  	public int find(Map<String, String> itemList);  	public int find(int parentID, Map<String, String> itemList);  	  	public List<Integer> findAll(Map<String, String> itemList);  	public List<Integer> findAll(int parentID, Map<String, String> itemList);  }  

在服務端實現這些介面,將實現類與介面類一一映射。這是通過RPC服務端的自定義servlet和配置文件所實現的,如清單7和8所示。


清單7 RPC 服務端自定義 servlet
public class MyServlet extends XmlRpcServlet {    	private static final long serialVersionUID = -8197532396221699878L;    	protected XmlRpcHandlerMapping newXmlRpcHandlerMapping()  		throws XmlRpcException {  	URL url = MyServlet.class.getResource("MyHandlers.properties");  	if (url == null) {  	   throw new XmlRpcException("Failed to locate resource MyHandlers.properties");  		}  		try {  			return newPropertyHandlerMapping(url);  		} catch (IOException e) {  	    throw new XmlRpcException("Failed to load resource " +   	                  url + ": " + e.getMessage(), e);  		}  	}  }  


清單 8 RPC 服務端介面映射配置文件
# autonotes.rpcserver.servlet.MyHandlers.properties  autonotes.control.interfaces.ButtonTesterProxy =                                    autonotes.control.impl.ButtonTesterProxyImpl  autonotes.control.interfaces.CanvasTesterProxy =                                    autonotes.control.impl.CanvasTesterProxyImpl  autonotes.control.interfaces.ComboTesterProxy =                                    autonotes.control.impl.ComboTesterProxyImpl  autonotes.control.interfaces.CompositeTesterProxy =                                    autonotes.control.impl.CompositeTesterProxyImpl  

自定義的 servlet 指定了配置文件的路徑並讀取該配置文件,從而生成介面類和實現類的映射。將對 RPC 服務請求映射成了對不同實現類的方法調用。在具體實現中,只需要實現所有的介面類,並將請求代理到 Abbot SWT 的類中即可,代碼片段如清單 9 所示。


清單 9 RPC 介面實現
public class WidgetTesterProxyImpl extends TesterProxy implements  		WidgetTesterProxy {  	private static final Class<?> widgetClass = Widget.class;    	public int actionClick(int id) {  		simpleInvokeForWidget(id, "actionClick", widgetClass);  		return IStatus.ACTION_SUCCESS;  	}    	protected Object simpleInvokeForObject(int id, String name,  			Class<?> actualClass, Class<?> paraClass) {  		return invokeForObject(id, name, null, null, actualClass, paraClass);  	}  protected Object invokeForObject(int id, String name,  Class<?>[] otherParaTypes, Object[] args, Class<?> actualClass, Class<?> paraClass) {  		checkWidgetID(id, actualClass);  		Widget w = (Widget) widgetTable.lookup(id);    		Class<?>[] paraTypes = null;  		Object[] realArgs = null;    		if (null == args) {  			realArgs = new Object[1];  		} else {  			realArgs = new Object[args.length + 1];  			System.arraycopy(args, 0, realArgs, 1, args.length);  		}  		realArgs[0] = w;    		if (null == otherParaTypes) {  			paraTypes = new Class[1];  		} else {  			paraTypes = new Class[otherParaTypes.length + 1];  			System.arraycopy(otherParaTypes, 0, paraTypes, 1,  					otherParaTypes.length);  		}  		paraTypes[0] = paraClass;    		return invoke(name, paraTypes, realArgs, actualClass);  	}  	protected Object invoke(String name, Class<?>[] parameterTypes,  			Object[] args, Class<?> widgetClass) {  		Method method = null;  		String exceptionMsg = "Exception when invoke method: \"" + name  				+ "\" of class " + widgetClass.getName();  		WidgetTester tester = WidgetTester.getTester(widgetClass);    		try {  			method = tester.getClass().getMethod(name, parameterTypes);  		} catch (SecurityException e) {  			log.error(exceptionMsg, e);  			throw new InvokeException(name);  		} catch (NoSuchMethodException e) {  			log.error("no such method: " + name + " in class: "  					+ widgetClass.getName(), e);  			throw new InvokeException(name);  		}    		try {  			return method.invoke(tester, args);  		} catch (IllegalArgumentException e) {  			log.error(exceptionMsg, e);  			throw new InvokeException(name);  		} catch (IllegalAccessException e) {  			log.error(exceptionMsg, e);  			throw new InvokeException(name);  		} catch (InvocationTargetException e) {  			log.error(exceptionMsg, e);  			throw new InvokeException(name);  		}  	}  

清單 9顯示了 WidgetTesterProxy 的 actionClick 方法的實現,actionClick 方法的輸入就是被測控制項的 ID,其作用是點擊該控制項,該方法首先將 ID 映射到實際的控制項對象中,然後調用 Abbot SWT 的 WidgetTester.actionClick 方法來點擊該控制項。所有的代理介面類都可以這樣實現,其大大簡化了 RPC 的實現。

在 RPC 客戶端只需重用服務端所提供的介面類,然後通過 Apache XML-RPC 所提供的代理類廠創建代理類對象,就可以直接調用 RPC 方法了,其代碼片段如清單 10 所示。


清單 10 RPC 動態代理客戶端
		ClientFactory factory = new ClientFactory(getClient());  		ClassLoader cl = Thread.currentThread().getContextClassLoader();  		ClassLoader cl2 = WidgetFinderProxy.class.getClassLoader();  		Thread.currentThread().setContextClassLoader(cl2);    		finder = (WidgetFinderProxy) factory  				.newInstance(WidgetFinderProxy.class);  		widgetTester = (WidgetTesterProxy) factory  				.newInstance(WidgetTesterProxy.class);  		shellTester = (ShellTesterProxy) factory  				.newInstance(ShellTesterProxy.class);         //... Other dynamic class instance           // find a shell object and active it  		Map<String, String> conditions = new HashMap<String, String>();  		conditions.put("class", "Shell");  		conditions.put("text", "RCP Product");  		int mainwin = finder.find(conditions);  		shellTester.forceActive(mainwin);          //...  

這樣,通過動態代理技術,我們很方便地實現了 RPC,在服務端, RPC 代理類將每次的調用轉化成 Abbot SWT 的每次調用,在客戶端,通過代理類介面和被測控制項的 ID ,只需要 Abbot SWT 單元測試用例基本類似的代碼,就能實現遠程的測試。這類似於測試程序遠程地調用 Abbot SWT ,將測試代碼的在其他進程中運行。





部署遠程測試實例

一個完整的部署本文所介紹的遠程測試方法需要如下步驟:

  1. 在被測的Eclipse軟體中安裝 Abbot SWT 和autonotes 這兩個插件

下載本文附件中的 abbot.swt.jar 和 autonotes.jar 這兩個插件,安裝到被測的 Eclipse 程序的 plugin 目錄中。我們以一個簡單的富客戶端程序來作為被測程序,安裝后在插件列表中能看到如圖 5所示的界面:


圖 5. 安裝 abbot.swt 以及 autonotes 插件

  1. 開發測試用例

在另外一台機器上(也可以是本地),下載本文附件中的 autonotesClient 工程,在 Eclipse 導入該工程,在其中添加一個測試類 simpleTest.TestCase1,繼承自 NotesTestCase 類,如清單 11 所示。


清單 11 TestCase1類定義
package simpleTest;    import static org.junit.Assert.assertEquals;  import java.util.HashMap;  import java.util.List;  import java.util.Map;  import org.junit.Test;    public class TestCase1 extends NotesTestCase{    }  

添加測試用例代碼,在函數中添加 @Test 註釋,這是因為該用例在 JUnit4 中運行。其代碼如清單 12 所示:


清單 12 第一個遠程測試用例
	@Test  	public void activeAndkeystroke() {  		Map<String, String> conditions = new HashMap<String, String>();  		conditions.put("class", "Shell");  		conditions.put("text", "RCP Product");  		int mainwin = finder.find(conditions);  		shellTester.forceActive(mainwin);  		  		conditions.clear();  		conditions.put("class", "Widget");  		List<Integer> text = finder.findAll(mainwin, conditions);  		for(int i : text) {  			System.out.println(widgetTester.getClassName(i));  		}  		  	int menubar = shellTester.getMenuBar(mainwin);  	for(int ii = 0; ii < 5; ii++) {  		if(mainwin > 0) {  		menuTester.actionClickItem(menubar, "Help/About RCP Product");  				shellTester.waitVisible("About RCP Product");  				conditions.clear();  				conditions.put("class", "Shell");  				conditions.put("text", "About RCP Product");  				int aboutDlg = finder.find(conditions);  				if(aboutDlg > 0) {  //assertEquals(textTester.getText(text), "RCP Mail template created by PDE");  					  					conditions.clear();  					conditions.put("class", "Button");  					conditions.put("text", "OK");  					int button = finder.find(aboutDlg, conditions);  					buttonTester.actionClick(button);  				}  			}  		}	   	}  

該用例重複 5 次自動化操作,每次首先點擊 Help->About RCP Product 菜單,然後等待 About RCP Product 對話框出現,最後點擊該對話框中的 OK 按鈕。

如果該用例需要在其他機器中遠程執行,需要修改樣例代碼中的 NotesTestCase 中的 getURL 方法,在其中定義被測程序所在的地址,如清單 13 所示。


清單 13 配置 URL
	protected static URL getURL() throws MalformedURLException {  		return new URL("http://127.0.0.1:" + getPort());  //本地運行  //		return new URL("http://192.168.1.10:49887");       //遠程運行  	}  

  1. 運行測試用例

在 Eclipse 中打開運行配置對話框,新建一個 JUnit 運行配置,制定運行用例,設定 JUnit4 作為單元測試運行器,如圖 6 所示:


圖 6. 運行配置對話框

點擊運行,該用例就可以遠程測試部署在另外一台機器上的 Eclipse 程序了。JUnit4 將運行該用例類,而該用例通過 XML-RPC 的代理類,和其他進程中的 Eclipse 程序中 autonotes 插件通信,遠程調用和被測程序處在同一進程的 Abbot SWT 庫,從而實現了遠程 GUI 功能自動化測試。

本文中樣常式序只是一個簡單的原型,其中包括了自動化測試服務插件 autonotes 以及一個測試用例支持包 autonotesClient。我們只需要基於 autonotesClient 工程,就能創建基於自己的測試用例,而只需要將 abbot.swt 插件和 autonotes 插件安裝在被測程序中,就能和基於 autonotesClient 工程的測試用例進行 XML-RPC 通信,進行各種 GUI 功能測試。





總結及展望

本文介紹了一種結合 XML-RPC 和 Abbot SWT 庫來進行 Eclipse 程序的遠程測試的技術。該技術不僅僅適用於測試一個遠程的 Eclipse 程序,還可以在一次測試用例同時測試2個甚至更多機器上的 Eclipse 程序,這樣大大擴展了 Abbot SWT 的功能,將其只能從一個本地的單元測試工具擴展到一個跨進程,跨網路的自動化測試工具。遠程測試尤其適用於網路中的協作軟體的功能自動化測試,例如即時通信,網路協作,富客戶端程序等等。本文所提供的樣常式序雖然只是一個原型,但已近具備了完整的遠程自動化測試 Eclipse 程序的功能,有興趣的作者可以進一步擴展該庫,使之功能更加完善。(責任編輯:A6)



[火星人 ] 用 XML-RPC 和 Abbot 來進行 Eclipse 程序的遠程測試已經有785次圍觀

http://coctec.com/docs/linux/show-post-69111.html