了解全新的 Eclipse 包管理机制

使用 Eclipse-LazyStart 节省基于 RCP 的应用程序的测试时间

了解如何通过支持 OSGi 命令 installssstartstopheadersactiveupdateuninstall 弥补 IBM® Rational® Functional Tester 和基于 Eclipse 的产品的控制台之间的不足。本解决方案提供了一种有效的方法,用于当 Eclipse-AutoStart 头部(header)的清单文件(manifest)被升级到 Eclipse-LazyStart 时提供自动测试用例支持。本文展示了一些测试场景,以验证这种包管理机制是可行的。

Xing Xing Li (lixx@cn.ibm.com), 软件工程师, IBM

Xing Xing LiXing Xing Li 于 2004 年作为一名 IBM Lotus 软件工程师加入位于中国北京的 IBM 中国软件开发实验室(IBM China Software Development Lab)。他主要关注在 Eclipse 平台上进行软件开发和测试以及 Java 技术。同时他对用户界面设计、设计模式以及算法研究也很有兴趣。他从重庆大学获得计算机科学硕士学位。



Yu Peng (pengyu@cn.ibm.com), 资深软件工程师, IBM

Yu PengPeng Yu 于 2004 年作为一名软件工程师加入位于北京的 IBM 中国软件开发实验室的 Lotus 部门。他主要关注在 Eclipse 平台上进行软件测试和 Java 技术。同时他也对用户界面设计、设计模式和算法研究很有兴趣。他从南开大学获得计算机科学硕士学位。



Jian Lin (jianlin@cn.ibm.com), 经理, IBM

  Jian LinJian Lin 是 IBM 中国软件开发实验室 Pervasive Computing Device Software Development and Services 的经理。



2008 年 6 月 17 日

当我们的测试团队升级到 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 控制台

因此,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 包状态
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 withcontainsperfect match

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

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

图 4. Install 命令场景
Install 命令场景

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

图 5. ss 命令场景
ss 命令场景

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

图 6. Sstart 命令场景
Start 命令场景

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

图 7. Headers 命令场景
Headers 命令场景

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

图 8. Active 命令场景
Active 命令场景

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

图 9. Active 命令场景
Active 命令场景

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

图 10. Update 命令场景
Update 命令场景

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

图 11. Uninstall 命令场景
Uninstall 命令场景

结束语

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


下载

描述名字大小
样例代码os-eclipse-bundlemgmt_source.zip34KB

参考资料

学习

获得产品和技术

讨论

  • Eclipse Platform 新闻组 应该是您讨论有关 Eclipse 问题的第一站(选择此链接将会启动默认的 Usenet 新闻阅读器应用程序并打开 eclipse.platform)。
  • Eclipse 新闻组 为对使用和扩展 Eclipse 感兴趣的人员提供了很多资源。
  • 参与 developerWorks blog 并加入 developerWorks 社区。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=314560
ArticleTitle=了解全新的 Eclipse 包管理机制
publish-date=06172008