`

了解全新的 Eclipse 包管理机制

阅读更多
了解如何通过支持 OSGi 命令 install、ss、start、stop、headers、active、update 和 uninstall 弥补 IBM® Rational® Functional Tester 和基于 Eclipse 的产品的控制台之间的不足。本解决方案提供了一种有效的方法,用于当 Eclipse-AutoStart 头部(header)的清单文件(manifest)被升级到 Eclipse-LazyStart 时提供自动测试用例支持。本文展示了一些测试场景,以验证这种包管理机制是可行的。
当我们的测试团队升级到 Eclipse V3.2 后,我们很快发现在我们的测试用例中 Eclipse 不再支持 AutoStart 头。Eclipse Foundation 已经使用 LazyStart 代替了 AutoStart,采用了 OSGi R4.1 规范中的延迟激活(lazy-activation)策略。这个更改带来的不利之一是,我们发现很难触发 LazyStart 中自动化的目标包。遗留的自动化测试用例需要公开一个资源,以便由触发包进行加载。由于更新所有遗留测试用例将会十分笨拙并且要花费大量时间,我们决定采用其他方法。

一种可行的方案是 Eclipse 控制台,一个操纵包的生命周期管理的强大工具。但是,Rational Functional Tester 不能识别 Eclipse 控制台。我们的解决方案是设计并实现该 GUI Console。

自动化测试的不足

Eclipse 是一个用于开发应用程序的流行的集成开发环境,得益于 Rich Client Platform (RCP),Eclipse 成为越来越多的应用程序的运行时平台,包括 IBM Notes® Client、Sametime® 以及 Expeditor(如果您刚接触 Eclipse,需要有关 Eclipse 功能的背景知识,请查看 参考资料)。但是,在自动化测试用例实践中,测试人员在尝试利用自动化工具(比如 IBM Rational Functional Tester)为基于 Eclipse RCP 的产品开发自动化测试用例时,遇到了严重的问题(至于 Rational Functional Tester 的对象识别的详细信息,请下载 “Grabbing GUI objects with IBM Rational Functional Tester”)。

IBM Rational Functional Tester 无法识别 Eclipse 或基于 Eclipse 的产品的控制台。当自动化测试人员试图通过 IBM Rational Functional Tester 获取 Eclipse 的控制台对象时,它无法识别该控制台。结果,自动化测试人员无法继续完成后续的任务。图 1 是 IBM Rational Functional Tester 无法识别 Eclipse 的控制台的一个屏幕截图。Rational Functional Tester 识别控制台的预期操作应该是图 1 中围绕控制台的一个红色的矩形。当 Rational Functional Tester 识别出 Eclipse 中的其他小部件(比如任务窗口、菜单栏以及组合列表)时,我们会看到类似的东西。


图 1. Rational Functional Tester 无法识别 Eclipse 控制台



因此,Rational Functional Tester 无法识别 Eclipse 控制台将引起严重的后果,因为自动化工具需要该控制台来执行以下两项任务。

包操作
出于一些显而易见的原因,我们需要检查并跟踪基于 Eclipse RCP 的产品中的目标包的状态。但是没有控制台的帮助,自动化测试人员必须开发并部署新的手动测试。测试人员必须将旧的自动化测试转换为手动测试用例。测试场景必须安装(install)、卸载(uninstall)、启动(start)、停止(stop)并确定(target)RCP 应用程序中的包。这可以借助控制台的支持来完成。通常,使用场景会在控制台中调用这些命令,提供一种检索回传结果的机制。
收集诊断信息
当 Eclipse 遇到错误时,它应该向控制台输出异常和错误。这些信息对于诊断问题的根源是非常关键的。开发人员非常希望测试人员能够在每个软件问题报告中提供这样的信息,但是 Rational Functional Tester 在自动化测试中无法收集这些信息。这意味着测试人员需要手动重新运行测试来收集这些信息。对于长时间运行的紧急测试来说,这基本上是不可能的。
为弥补这个不足,我们提出了一个称之为 GUI Console 的解决方案。

需求识别

包生命周期管理:AutoStart vs. LazyStart

在 MANIFEST.MF header 中,AutoStart 和 LazyStart 可能被当作是同一包-清单文件 header 的同义词,不同之处是不推荐使用前者,而推荐使用后者。在修复 Eclipse V3.1.2 中的 537 个 bug 的过程中(这为我们带来了 Eclipse Europa),AutoStart 被放弃了。现在,全部都是关于 OSGi V3.2 遵从性和 LazyStart(请查看 参考资料 进一步了解包如何在 MANIFEST.MF 文件中包含有关其自身的描述信息)。

在 Eclipse V3.2 中,LazyStart header 定义包是否在其类之前自动启动或者某个资源是否被其他的包访问。通过将值设置为 true,我们可以在第一次加载类或资源时延迟激活包 — 换句话说,自动激活。

如前所述,即使将所有遗留的测试用例迁移到 LazyStart 在逻辑上是可行的,但是这么做非常笨拙,将花费巨大的努力。更新所有测试用例以支持 AutoStart,这将向它们公开一个资源,并通过一个触发包加载它们。另外,启动包没有覆盖我们的自动化测试用例的所有要求。包状态管理 — 或者说 “包生命周期操作”— 涉及测试用例中的验证点。

包生命周期管理:包状态

包(包括我们的自动化测试用例中的包)在同一时间只能处于以下六种状态之一。

INSTALLED
当包成功安装后它可以进入 INSTALLED 状态。
RESOLVED
当包所需的 Java 类可用时,它可以进入 RESOLVED 状态。换句话说,这种状态表示框架已经成功按清单文件中的描述解析了包的依赖性。RESOLVED 状态来自于 INSTALLED 或 ACTIVE 状态,可进入 ACTIVE。
STARTING
当包正被启动时可以进入 STARTING 状态(当 BundleActivator.start() 方法被调用,但是还没有返回时)。
ACTIVE
当包已经启动且正在运行中,此时可以进入 ACTIVE 状态。
STOPPING
当包正被停止时可进入 STOPPING 状态(当 BundleActivator.stop() 方法被调用,但是还未返回时)。
UNINSTALLED
当包已被卸载后可以进入 UNINSTALLED 状态。它不能进入其他状态。
Bundle 接口定义了一个 getState() 方法,用于返回包的状态。

图 2 图示了包在其生命周期中的所有状态,以及它的转换路径。


图 2. OSGi 包状态



检索清单文件 header

MANIFEST.MF 的 header 是该 GUI Console 基础的另一部分。Eclipse 希望包开发者在名为 MANIFEST.MF 的清单文件中提供有关包的说明信息。这是定义清单 header 的 OSGi 文件,比如 Export-Package 和 Bundle-Classpath 说明。表 1 列出了 OSGi 包中最有用的 header。


表 1. 某个 OSGi 包的 header
OSGi 包 header 说明
Bundle-Activator 指定用于启动和停止包的类的名称。
Bundle-Classpath 指定包含类和资源的 JAR 文件或目录。句号(‘.’)、默认值指定了包的 JAR 的根目录。
Bundle-ContactAddress 包含供应商的联系地址。
Bundle-Copyright 包含此包的版权说明。
Bundle-DocURL 指定了一个指向有关此包的文档的 URL。
Bundle-Localization 指定了包的本地化文件的位置,其默认值为 OSGI-INF/l10n/bundle。
Bundle-ManifestVersion 指定该包遵从 OSGi 规范 V3 或者 OSGi 规范 V4 中的规则。
Bundle-Name 指定该包的可读名称(无空格)。
Bundle-SymbolicName 一个强制的 header,用于为此包指定一个惟一的名称。
Bundle-Vendor 包含一个包供应商的可读名称。
Bundle-Version 指定包的版本,默认为 0.0.0。
Export-Package 指定此包的导出包(exported package)。
Fragment-Host 定义此片段的本地包(host bundle)。
Import-Package 声明此包的导入包(imported package)。
Require-Bundle 指定从其他包所需的导出。
Import-Service 不建议使用
Export-Service 不建议使用


需求总结

根据以上的需求分析,该 GUI Console 解决方案需要提供以下命令,以支持我们的自动化测试用例的 OSGi 包管理。


表 2. GUI Console 支持的 OSGi 命令
命令 说明
install+bundle URL 将一个具有指定 URL 的包添加到当前平台。
uninstall+bundle ID 从当前平台删除一个指定的包。
ss 列出在当前平台注册的所有包的简单状态。
start+bundle ID;
start+bundle name 启动具有给定包 ID 或符号名称的包。
stop+bundle ID;
stop+bundle name 终止具有给定包 ID 或符号名称的包。
headers 列出具有给定 ID 或符号名称的包的清单 header。
active 列出当前平台中所有活动的包。
update 更新一个当前实例的包。






设计和实现

在本节中,我们将展示为什么在我们的 GUI Console 解决方案中 BundleContext 对象和包对象非常关键,并且我们将讨论 OSGi 中的包管理。

BundleContext

BundleContext 将 Eclipse 框架和框架中安装的包连接了起来。BundleContext 对象代表 OSGi 平台内的包的执行上下文,并且作为底层框架的代理运行。

当包启动后,该框架将会创建一个 BundleContext 对象,作为参数提供给该包的 Bundle-Activator 的 start(BundleContext) 方法。该包可以使用这个私有的 private BundleContext 对象执行以下操作:

将新包安装到 OSGi 环境。
询问该 OSGi 环境中其他已安装的 bundle。
获得持久性存储区域。
检索已注册服务的服务对象。
在框架服务中注册服务。
订阅或取消订阅由框架广播的事件。
每个包都有自己的 BundleContext 对象,并且它们不应该在包之间传递。为什么?因为 BundleContext 对象与包的安全性和资源分配有关。当 Bundle-Activator 的 stop(BundleContext) 方法被返回时,BundleContext 对象就会停止服务。

BundleContext 最有用的一点就是,它定义了一些方法,用于检索有关安装在 OSGi 服务平台中的包的信息:

getBundle()
返回与 BundleContext 对象相关的单个包对象。
getBundles()
返回当前安装在框架中的一组包。
getBundle(long)
返回由惟一标识符指定的包对象,如果没有发现匹配的包,则返回空。
因为对包的访问没有限制,所以任何包都可以枚举已安装包的集合。这允许我们处理和控制自动化测试用例的包生命周期管理操作以及包信息检索操作。GUI Console 将使用清单 1 中的代码来检索所有活动包的信息。它的功能和 Eclipse 控制台的 active 命令是一样的。


清单 1. Active 命令实现代码
                
public static void doActive() throws Exception {
    b = bContext.getBundles();
    for (int i = 0; i > b.length; i++) {
        if (b[i].getState() == b[i].ACTIVE) {
	result = result + b[i].getLocation()+" " + "["+b[i].getBundleId() +"]"
                 +System.getProperty ("line.separator");
		    } 
		}
		result=result+p+" active bundle(s).";
	}
 



至于曾经很流行的 ss 命令,只有当我们接受所有具有相应状态的包(包括 INSTALLED、RESOLVED 和 ACTIVE)时,GUI Console 才可以根据以上代码实现它。有关详细信息,请参阅 样例代码。

包对象

为了在具有基于 Eclipse 的产品的 OSGi 平台中管理包的生命周期,我们必须对目标包使用相关的包对象。BundleContext 接口提供了以下安装包的方法。

installBundle(String)
从指定的位置字符串(一个 URL)安装包。
installBundle(String,InputStream)
从指定的 InputStream 对象安装包。
当包成功安装后,将为其生成一个包对象,所有的生命周期管理操作必须使用此对象执行,比如启动、停止和卸载。

如我们在清单 2 中所看到的,我们可以使用提供的位置字符串安装包。如果该包在平台中成功安装,它将返回该包的符号名称。


清单 2. Active 命令实现
               
public static String doInstall(String location) throws Exception{
                ...
		Bundle iBundle = bContext.installBundle(location);
		if(iBundle!=null){
			iBundle.update();
			return iBundle.getSymbolicName();
		}
		return null;
	}
 


启动包

包接口定义 start() 方法,用于启动包并将包从 RESOLVED 状态转换到 ACTIVE 状态。前提条件是,该包必须已经被成功解析;否则会抛出一个 BundleException。

要执行一个包的 start() 方法,我们需要通过该包的清单文件中的 Bundle-Activator header 通知 OSGi 环境 Bundle-Activator 的类名。OSGi 环境将会实例化该类的一个新对象,并将其传递到一个 Bundle-Activator 实例。然后它将会调用 BundleActivator.start() 方法来启动该包。

作为一个 Bundle-Activator,该包中的类必须实现此 Bundle-Activator 接口,将其声明为公共的和公共的默认构造函数。但是,在每个包中提供一个 Bundle-Activator 是可选的行为。例如,导出少量包(package)的 library 包并不需要定义 Bundle-Activator。

当提供了包 ID 或符号名称时,我们的 GUI Console 应用程序调用以下代码来启动包。


清单 3. Start 命令实现
               
public static void doStart(int bID) throws Exception {
    if(bContext.getBundle(bID)!=null&&bContext.getBundle(bID).state==
    bContext.getBundle(bID).RESOLVED){
	bContext.getBundle(bID).start();
    }
}
public static void doStart(String s,String matchS) throws Exception {
    b = bContext.getBundles();
    ...
    for (int i = 0; i < b.length; i++) {
	if (b[i].getSymbolicName().indexOf(s) >-1 && (b[i].getState() == 
        b[i].RESOLVED) {
	    boolean isFragment=false;
	    Enumeration eKey = b[i].getHeaders().keys();
	    Dictionary dValue = b[i].getHeaders();
	    Enumeration eValue = dValue.elements();
	    while (eKey.hasMoreElements() && eValue.hasMoreElements()) {
		String sKey = eKey.nextElement().toString();
		if (sKey.equalsIgnoreCase("Fragment-Host")) {
		    isFragment=true;
		 }
	    }
	   if (isFragment==false){
		b[i].start();
	   }
     }				    
}
 


停止包

包接口定义 stop() 方法来停止包,并导致转换到 RESOLVED 状态。所有与停止包相关的线程应该立即停止。

卸载包

包接口提供 uninstall() 方法来从框架中卸载包。该框架将通知其他包该目标包正在被卸载,删除所有与该包相关的资源,并将该目标包的状态设置为 UNINSTALLED。

新安装的包不能使用已卸载包的 package。但是,如果已卸载的包曾经导出过其他包使用的 package,那么框架将会继续使用这些 package,直到框架被重启或者 org.osgi.service.packageadmin.PackageAdmin.refreshPackages() 方法被调用。

以下代码展示了 GUI Console 如何使用提供的包 ID 或符号名称从平台卸载包。


清单 4. Uninstall 命令实现
  
             
public static void doUninstall(String s, String matchS) throws Exception{
    b = bContext.getBundles();
    ...
    if (b[i].getSymbolicName().indexOf(s) >-1) {
	....
	b[i].uninstall();
    }
    ...
}
public static void doUninstall(int bID) throws Exception{	
    if (bContext.getBundle(bID)!=null){
	bContext.getBundle(bID).uninstall();
    }
}



检索清单 header

包接口提供了两种方法来返回清单 header 信息:

getHeaders()
返回一个包含该包的清单 header 和值(键-值对)的 dictionary 对象。
getHeaders(String)
返回一个包含该包的清单 header 和值(键-值对)的 dictionary 对象。
即使当包进入 UNINSTALLED 状态时,getHeaders 方法可以继续提供清单 header 信息。


清单 5. Headers 命令实现
    
           
Enumeration eKey = b[i].getHeaders().keys();
Dictionary dValue = b[i].getHeaders();
Enumeration eValue = dValue.elements();
while (eKey.hasMoreElements() && eValue.hasMoreElements()) {
    String sKey = eKey.nextElement().toString();
    String sValue = eValue.nextElement().toString();
    headers.append(sKey+" = ");
    headers.append(sValue+"\n");
}





执行

我们可以通过使用 IBM Expeditor(一个基于 Eclipse 的产品)执行 GUI Console 测试场景(请参阅 参考资料)。它要求我们在验证场景之前将 GUI Console 安装到 Expeditor 或任何其他基于 Eclipse 的产品中。然后,如图 3 所示,我们使用一个叫做 PascalTriangle 的示例包作为执行期间的操作包。通过在文件系统中导出它的 JAR 文件,我们将在 GUI Console 上执行包管理操作,包括安装、启动、停止、卸载以及其他操作。

GUI Console 的一个优势是,包的符号名和包 ID 将自动在命令解释和执行期间获得支持。当安装好格式化为导出 JAR 文件的包时,符号名和包 ID 将通过编程的方式检索。另外,为了为自动化测试执行提供尽可能多的灵活性,您可以使用简单的 regex 技术搜索符号名称,包括 start with、contains 和 perfect match。


图 3. 操作包:为进行测试而导出的 Pascal 包 JAR 文件



为了展示支持 OSGi 的 install 命令的功能,我们将把这个操作包导入到 Expeditor 或者任何其他基于 Eclipse 的产品,它们已经配备了 GUI Console 应用程序。在 GUI Console 应用程序中,按 Browse,导航到 PascalTriangle bundle JAR 文件在文件系统中所在的位置,然后按下 OK。在文本字段中检验了目标包的位置地址后,按下 Install。如果 GUI Console 和 OSGi 平台可以成功加载和解析您的 PascalTriangle 包,您将会看到指定了一个包 ID。


图 4. Install 命令场景



使用 ss 命令(按下 ss 按钮),检查包的状态是否被解析,如下所示。


图 5. ss 命令场景



要启动包,在安装好该包并且文本字段自动检索到它的符号名称后按下 Start。启动 PascalTriangle 包的执行结果可以按如下所示进行检验。


图 6. Sstart 命令场景



要查看一个包的 MANIFEST 文件中 header 的详细信息,请按 headers 按钮。随后,您可以查看您的目标包中所有可用的 header 和它们的值。图 7 展示了对 PascalTriangle 包执行 headers 命令的结果。


图 7. Headers 命令场景



有时候您需要列出平台上所有 ACTIVE 状态的包。要进行概览,按下 Active。详细信息请参阅图 8。


图 8. Active 命令场景



如下所示,在显示区域的底部列出了 GUI Console 包和 PascalTriangle 包,包括它们的包 ID。


图 9. Active 命令场景



图 10 也展示了使用 Update 命令把目标包的 JAR 文件替换为一个更新的文件。我们可以发现更新的 PascalTriangle 在更新之后被输出。


图 10. Update 命令场景



卸载包的操作如下所示。结果由 ss 命令进行了验证,按照符号名称搜索 PascalTriangle 包。正如我们可以看到的,在卸载后 OSGi 平台中就没有 PascalTriangle 包存在了。


图 11. Uninstall 命令场景







结束语

IBM Rational Functional Tester 无法识别 Eclipse 控制台。从 V3.2 开始,Eclipse 不再支持遗留测试用例中的 Eclipse-AutoStart header。为了弥补这个不足,我们展示了一个称为 GUI Console 的解决方案,它可以使用新的 Eclipse-LazyStart。


分享到:
评论

相关推荐

    JAVA上百实例源码以及开源项目源代码

    Java生成密钥的实例 1个目标文件 摘要:Java源码,算法相关,密钥 Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、...

    java源码包2

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    数据库课程设计-飞机订票系统.doc

    1.2 编写目的 编写此项目的目的是为了进一步了解数据库的储存管理机制以及数据库与其他的语 言语言工具之间关联和协作。也可以熟悉项目开发的流程,步骤,为以后编写其他 的程序打下基础。 1,了解并掌握数据结构的...

    java源码包3

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java源码包4

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    Android应用程序开发教程PDF电子书完整版、Android开发学习教程

    Dalvik 虚拟机依赖于 linux 内核的一些功能,比如线程机制和底层内存管理机制。 Linux Linux Linux Linux 内核 Android 的核心系统服务依赖于 Linux 2.6 内核,如安全性,内存管理,进程管理, 网络协议栈和驱动模型...

    J2ME 中文教程1.01a

    第五章“MIDP的持久化解决方案 — RMS” 为我们讲解了数据持久化机制——记录管理系统(Record Management System RMS)。这一特别的小型数据库使得MIDP的数据保存变得很特别。 第六章“GAME API” 介绍了MIDP 2.0...

    java源码包---java 源码 大量 实例

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    数据库课程设计-图书馆管理信息系统(1).doc

    1 任务陈述 图书馆管理信息系统的主要任务是对馆内的书籍资源,读者资源,借书信息,还书信息进 行管理,便于及时的了解各个环节信息的变更。 2.2 任务目标 维护(添加,删除,修改)管理员信息 维护(添加,删除,修改...

    J2ME 中文教程

    第五章“MIDP的持久化解决方案 — RMS” 为我们讲解了数据持久化机制——记录管理系统(Record Management System RMS)。这一特别的小型数据库使得MIDP的数据保存变得很特别。 第六章“GAME API” 介绍了MIDP 2.0...

    J2ME 中文版教程

    第五章“MIDP 的持久化解决方案 — RMS” 为我们讲解了数据持久化机制——记录管理 系统(Record Management System RMS)。这一特别的小型数据库使得MIDP 的数据保存变得很特 别。 第六章“GAME API” 介绍了MIDP 2.0...

    JAVA上百实例源码以及开源项目

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    数据库课程设计-图书馆管理信息系统.doc

    开发工具: eclipse 数据库: SQL Server 2000 操作系统: Windows XP 二、数据库规划 2.1 任务陈述 图书馆管理信息系统的主要任务是对馆内的书籍资源,读者资源,借书信息,还书信息 进行管理,便于及时的了解各个...

    j2me 中文教程 开发环境 J2ME语言

    第五章“MIDP 的持久化解决方案— RMS” 为我们讲解了数据持久化机制——记录管理 系统(Record Management System RMS)。这一特别的小型数据库使得MIDP 的数据保存变得很特 别。 第六章“GAME API” 介绍了 MIDP 2.0...

    新版Android开发教程.rar

    � Eclipse JDT plugin (included in most Eclipse IDE packages) � WST (optional, but needed for the Android Editors feature; included in most Eclipse IDE packages ) o JDK 5 or JDK 6 (JRE alone is not ...

    J2ME中文教程

    第五章“MIDP 的持久化解决方案 — RMS” 为我们讲解了数据持久化机制——记录管理系统(Record Management System RMS)。这一特别的小型数据库使得MIDP 的数据保存变得很特别。 第六章“GAME API” 介绍了MIDP 2.0 ...

    java流程分析(SOPA)

    SOPA为分析java流程的eclipse插件,其主要用途如下: 1. 孙子曰:凡治众如治寡,分数是也。每个交易所涉及的方法众多,如何才能整体把握所用到的方法很关键。流程分析图将众多方法进行层次整理,使开发人员方便的...

Global site tag (gtag.js) - Google Analytics