用SWTBot+Junit+Truth做GUI层面的Unit Testing

在开发组里,Unit Testing的方法已经深入人心,Case的数量也越来越多。可因为GUI层面代码的特殊性,目前大多数的测试都针对非GUI层面的Code。这使得占总代码量40%的GUI层面很少被单元测试覆盖。

本文通过一个简单的例子,结合SWTBot,Junit和Truth,实现了GUI层面的单元测试。

SWTBot的安装

SWTBot可以通过Update Site(http://download.eclipse.org/technology/swtbot/releases/latest/)安装。

在开发项目中也要加入SWTBot的依赖,

一个简单的Dialog

让我们来实现一个简单的对话框,用来做两个数的加法。

代码如下:

这里特别使用了setData方法对几个关键的控件设置了“id”,这是为了能在SWTBot中更方便准确的定位待测试的控件。

SWTBot+JUnit+Truth

待测的对话框已经准备好了,下面来写一个简单的单元测试。

几点要注意的地方,

  • 测试Dialog时,要使用setBlockOnOpen(false),否则open()方法会把后续测试代码阻塞掉。
  • SWTBot搜索控件的办法有几种,以Text控件为例,介绍几个常用的。
    • text()等价于text(0), 就是找第一个Text控件
    • text(n), 按顺序找第n个控件
    • textWithLabelInGroup(label, inGroup)等价于textWithLabelInGroup(label, inGroup,0),就是找在某个Group里的第0个Label为”label”的Text控件
    • textWithLabelInGroup(label, inGroup,n),就是找在某个Group里的第n个Label为”label”的Text控件
    • textInGroup(text, inGroup)等价于textInGroup(text, inGroup, 0), 找在某个Group里第0个text为“text”的Text控件
    • textInGroup(text, inGroup, n), 找在某个Group里第n个text为“text”的Text控件
    • textWithId(key, data), 找出key=data的Text控件。这就是和前面setData()相对应的一个API。用ID找还有一个更简单的API–textWithId(value),这个API没有输入key,原因是SWTBot有个Preference给DEFAULT_KEY–org.eclipse.swtbot.search.defaultKey,默认的值为
      “org.eclipse.swtbot.widget.key”
  • 对不同的控件,API会略有不同,不过大同小异。SWTBot现在除了支持基本的SWT控件外,还支持了Nebula Grid,NatTable,GEF等复杂的控件。
  • 写Assertion的部分和普通的Unit Test并没有什么不同。
  • 因为这种Unit Test的写法和普通的JUnit并没有什么不同,所以Headless Build就可以用一般maven test。当然,如果是开发RCP应用,一定要使用Eclipse Tycho插件。
  • 还有一点不同的事,SWTBot的Case一定要在X环境下。如果Build Server上并没有开X环境,需要要安装Xvfb

总结

通过一个小例子,本文讨论了如何使用SWTBot对小型的SWT开发单元,如对话框、Composite等,进行单元测试(SWTBot也可以测试完整的大型应用)。

现在可以慢慢完善GUI层面的单元测试了,下一步也许可以和Cucumber之类的Framework结合起来。😁

 

 

 

不要放过SWT里的Resource Leak

最近出了几个奇怪的问题,打开时间的GUI会莫名的抛Exception,报“no more SWT handle”. 句柄(Handle)资源以前做Win32/MFC接触的比较多,转到Java/SWT之后就很少接触到。

出错原因很清楚,Resource不够了!SWT和Swing的实现方法不同,它还是使用了Native的资源,所以句柄资源不会被GC回收。Eclipse提供了一个叫Sleak的工具来检查Leak的情况。情况果然非常严重,主要原因是我们生成了大量的Image。不仅仅是Image,在使用Font,Color,GC的时候也要非常注意,遵循“谁生成谁销毁”的原因。

在RCP中加入Sleak的方法是–Application.java的start方法中加入:

之后,Sleak的界面会随着RCP启动。

Reference

在Eclipse RCP中集成HTML/CSS/Javascript (Integrate HTML/CSS/Javascript in Eclipse RCP)

新项目对UI的要求很高,产品负责人常常要求实现比较Fancy的功能。基于Eclipse RCP平台,用SWT/JFace实现起来比较麻烦,而且组里人手也不够,逼得我们常常要找替代方案。

Eclipse RCP虽然在Look & Feel上有所提高,但它的强项且不是前端,而是在于代码组织(模块,服务),功能扩展等方面。但如果能在RCP环境中集成Web前端的技术(HTML/CSS/Javascript),那实现这些Fancy的功能就容易的多。顺着这个思路我做了些实践,做了一个小页面,可以选择本地图片,再做PS处理。

webkit_sample

基本方案 – 使用Browser控件

SWT支持Browser控件,到3.7M2之后开始支持各平台下WebKit。各平台对WebKit要求不同,要参考SWT FAQ。我的体会是Mac(Safari),Linux平台(GTK+ WebKit)很好,Windows比较差。此控件也支持MOZILLA(比较旧的版本),但效果很差,很多CSS库无法运行。(本文多在Linux下WebKit的实践。)

首先要嵌入Browser,

之后,Browser就会显示Google主页的内容。

本地的HTML/CSS/JS文件

当然我们不是要做真正的浏览器,目标是使用本地的文件。实现思路也很清楚,就是要访问RCP Bundle里的文件。RCP也是支持的,可以用如下代码。

假设有如下目录结构:

web_folder

但这里要注意一个问题,我们的HTML常常会用到JS或CSS等外部文件。如果这些文件全都打包在Jar文件里,HTML文件内容可以被正常访问,但CSS等就无法被引用了。

解决的方法也很简单,就是不打包这部分文件,可以使用Feature加Fragment的方法。

code_structure

webkit_test是Host Plugin, webkit_test_webfiles是它的Fragment,webkit_test_feature是包括这两个Plugin的Feature。在Feature的设置中,打开“Unpack这个Fragment在安装时”。这样就可以得到一个Folder而不是Jar。

feature_setup

如何和Java代码互通呢?

这是个大问题。用了HTML,那我们要不要写个Server或者某个嵌入式的Server呢?如果需要,方案就会比较复杂,毕竟RCP还是专注于桌面端的(虽然有RAP,那是后话)。但当我发现BrowserFunction后,这个问题就简单了。

假如,HTML想通过一个函数得到RGB中红、绿、蓝的值,这些值却是在SWT的Text中输入的。函数分别是r(), g(), b().

在RCP的Java代码中,

BrowserFunction构造的第一个入参是Browser Instance,第二个是函数名。这可以大大扩展JS的能力。

那SWT可以控制Brower吗?比如调用JS里的Function。答案也是可以的。首先Browser提供了很多的Listener,让SWT捕获各种事件,此外可以直接用Browse.excute()来执行JS Function。

比如JS中定义了draw()函数,Java代码就可以用如下代码来调用 。

总结

在Sample中,我集成了Two.js,jquery, jquery-ui和AlloyImage.js, 实现了一些图片选择处理的功能,Webkit都能工作良好,让我惊艳,也让我坚信这个方向探索的潜力。但也遇到了不少问题,如Resize时页面的刷新问题,如何调试HTML页面,跨平台问题等,还需要更多时间的探索。Sample的代码在GitHub上,在Ubuntu 13.04, Mac OS X 10.8上都可以工作良好。

第一个自定义SWT控件 – Advanced Navigator Bar

新项目已经开始,GUI上的改变还是一如既往的大,多,快!做GUI的真苦逼!

因为产品中有某种流程的概念,Boss需要一种能显示流程顺序且兼有Navigator功能的Bar。它在界面底部,用户可以用来做页面跳转。

Eclipse RCP使用SWT作为其图形库。在一顿乱Google之后,没有发现有现成的控件,所以只能自己做一个了。其实不是很想做,但作为一名GUI工程师,也不能总用标准控件吧。其实做控件并不难,以前在用wxWidgets,也做过几个控件,其实要点就几步,主要是费时调试。想想那会为了个控件调到凌晨4、5点,心里就觉得酸,:(。但这几个我费心做出的控件一直在产品中使用,算是一种安慰吧!

在Eclipse Corner Article里,找了篇文章(在Reference里),感觉和wxWidgets差不多。毕竟还是一个简单控件,试了一下,成功了, 同时感慨SWT有很多牛X的功能,远不是wxWidgets可以比的。

归纳几个要点。

控件重载

做自定义控件的第一步就是重载。SWT有两个比较适用的父类,

  • org.eclipse.swt.widgets.Canvas
  • org.eclipse.swt.widgets.Composite

Composite主要用来把其他控件compose在一起;Canvas则在给你提供一个画布,所有的显示行为都自己定义。这里显然Canvas更适合我。

计算大小

Parent控件或container在使用你的控件时,并不知道你的控件应该有多大。所以我们要提供Preferred Size。

在SWT中,你要重载compusiteSize(int wHint, int hHint, boolean changed). 这部分要根据控件中不同的内容来定,如图的大小,字符串的显示大小,它们怎么组织的。花点时间总是可以写出来的。

提一点,在这个函数中,你可能需要算字符串的长宽,所以你需要GC,所以你可以new一个然后用stringExtend计算大小。

重绘

有了大小了,下面就要告诉别人怎样把自己画出来。SWT里的办法是增加一个PaintListener.

在paintControl函数里,你就尽情地在GC上画吧。

事件响应

显示出来之后,和用户的交互也很重要。这里就要增加键盘和鼠标的事件。我的控件比较简单,只有鼠标事件。处理过程就是

  1. 响应鼠标事件
  2. 做点击测试
  3. 转换事件
  4. 找事件监听器处理

首先响应鼠标事件,

点击测试就是找出鼠标事件发生在哪个区域,里面会有一些Rectangle.contains()等类似的函数。有兴趣请在后面的Github上看看我的代码。

事件转换就是当你识别出事件后,把原来的鼠标事件做一个翻译包装,然后转发。比如在我的控件里,我要识别用户点在哪个Flow的节点上,所以我包装了一个节点变化的事件。

再做一个监听器给外部代码,某种简单的Observer的模式。

在控件里添加add/remove的接口,

其他要点

这几步就是最重要的。还有一些小的要点,

  • SWT中要注意把Color,Font等资源回收(dispose)
  • SWT可以用gc.setAntialias(SWT.ON)打开抗锯齿
  • 为了避免控件闪烁(flickering),特别是在频繁Resize里,可以在构造控件里加上SWT.DOUBLE_BUFFERED的style。

后两项都是wxWidgets无法比的,需要比较复杂的实现。

Github

代码还在准备中,很快上传。。。

References