用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结合起来。😁

 

 

 

Java坑之Class.isEnum()

发现在这个坑是因为代码里用到了cloning库

在做深度代码拷贝时,发现Enum对象也被Copy了,破坏了Enum对象的唯一性。读了cloning的源代码,其实它考虑了Enum的问题,默认也是跳过的。在其1.9.0版本,它使用了Class.isEnum()来检查,但在1.9.2版本中,检查的代码换成了instanceof Enum, 问题也解决了。对比一下,两种API的区别,

输出是

第二行的输出居然是false, 再看看A2的类签名,不是A,而是一个匿名类A$1。这都是因为A2有Override toString方法,Java产生的了一个匿名类。再看看isEnum的实现,

这下都清楚了,JDK里使用通过基本类是否是Enum的方法,判断当前类是否是Enum, 但当有匿名类的出现,父类就不再是Enum,isEnum的检查也就失效了。

难道Enum的匿名类就不是Enum了吗?😈

这个坑大家要小心啊!

使用Java Dynamic Proxy API实现简单的Function Decorator

Python里有非常方便的Function Decorator语法糖,使用起来非常简便。如果想在Java里做类似的事情,可以使用Dynamic Proxy API来实现。

看看这个小例子。有一组Web服务接口,需要访问数据库得到所需的数据。比如,

代码里的Analyzer类会去数据库找到相应的数据。

为了提高访问的效率,加入memcached,在真正的数据库访问之前,先去查看有没有相应的Cache,代码会变成类似的样子,

如果每个服务接口都要做同样的事,可想而知,会有非常多的重复代码。这时就可以利用Dynamic Proxy API来大大的优化代码实现,非常灵活。

Dynamic Proxy API只能针对接口,所以要为原来Analyzer类写一个接口类,如:

然后我们的代理实现类要继承自InvocationHandler,并通过Proxy.newProxyInstance生成真正的代理。看下面的实现,

有了动态代理,原来Web服务接口的实现就非常简洁了。

Java自带的Dynamic Proxy API虽然有些限制,但使用起来还是非常简单的。听说还有更强大的CGLib,有机会现研究一下。😄

Eclipse里生成Guava-based的hashCode()和equals()

做Java开发的常常要重写类的hashCode()和equals()。一般情况下,可以套常用的模板。Eclipse就套用这些模板,直接生成默认实现,可实现比较冗长,比如下面的例子

有类A,

Eclipse的默认实现是这样的,

显然实现比较冗长,也比较乱,包含了很多细节,有些也不符合代码规范(比如if后没有花括号)。

用过Guava的同学都知道,使用Objects类可以大大简化这两个函数的实现。我试着扩展了Eclipse JDT,套用Guava的实现模板,那代码就漂亮多了。Guava隐藏了大量的实现细节。

如下,

具体的代码实现在Github上,点这

主要分如下有几步,

  • Project Explorer的上下文菜单中加入新的功能选项。
  • 通过Eclipse里事件得到Java CompliationUnit,使用ASTParser得到类里的Non-Static Member Field,然后就套用新模板吧。
  • 一个源文件中可能包含多个Java Class,所以还要有个Dialog来选择在哪个类中实现方法 。

其他细节都在代码里的,有兴趣的就看看吧。😁

聊聊ClassLoader in Java World

ClassLoader是Java技术中非常主要的概念,原因是它用来加载Java Class。如果没有Class,也就没有对象;没有对象,就没有之后的一切了。

Java ClassLoader的默认实现是一种双亲结构,即总是尝试让Parent ClassLoader加载类。这样有一些优点,当然也有很大的限制,比如就不利于不同Module对多版本类的加载。更多的细节,在下面我做的PPT里。

还有些新的理解和发现。

  • 自定义的ClassLoader可以把Parent ClassLoader设置为null,如果再把loadClass函数重写,就可以完全屏蔽default classLoader的影响。如OSGi的Bundle ClassLoader的最终的Parent就不是Bootstrap ClassLoader(Bundle ClassLoader的实现还是会去从默认ClassLoader去加载java.*的类)。
  • 程序执行时,ClassLoader的选择。做几个小实验你就会发现,默认的ClassLoader是“加载当前执行函数所在类的ClassLoader”。在OpenJDK的源码中一通Search,就可以找到一些端倪。

在源文件src\interp\engine\interp.c中,有executeJava()函数中,执行ByteCode之前,先准备了MethodBlock的指针,它中间

可以看看GETSTATIC这个指令的实现(此指令会在读取类的静态方法时调用)。可以看到resolveField()方法,并在mb的Class指针传入。idx是Static Field对应Constant Pool里的index。

resolveField中被调用resolveClass函数

resolveClass又回调用findClassFromClass(其实是个Macro),同样传入class指针。看看Macro的定义,就可以很清楚了。

是从ClassBlock结构中找到对应的ClassLoader.

其实记住这个结论就好。这样就可以完全打错传统的ClassLoader的限制,比如OSGi Plugin/Bundle里的所有类就是由OSGi自己的加载器加载的,非常灵活,可以实现非常牛逼的功能!

 

在单元测试中使用PowerMockito隔离static native method

在单元测试中,如果被测类使用了某些static native接口,会使测试不太好写,因为Native API需要装载某使用Native库。我们可以使用强大的PowerMockito对这些接口进行隔离。

下面是个小例子。

Class with Native Static Method

Class which consumes the native static methods

Unit Test

Mock了Static Native方法,并做了Verify.

这只是个简单的例子,实际运用中Native方法往往比较复杂。在写单元测试时应该明确被测的对象,把不必要的接口Mock起来,会使测试变得非常清晰。

PS: 此例的Maven dependencies

  • junit:junit:4.8.1
  • org.powermock:powermock-mockito-release-full:1.6.2

别忘了在RCP App中配置自己的Keybinding Scheme!

在Eclipse RCP的基础上开发,我们会感到非常便利,有好多可重用的组件。但有时也有些意想不到的问题,  Key bindings就是其中之一。

一般情况下,我们通过扩展org.eclipse.ui.bindings来增加新的快捷键绑定。下面的例子中,我设置了一个CTRL+T的快捷键。

6-15-2015 2-25-23 PM

6-15-2015 2-25-47 PM

乍看上去,编译使用都能够做良好,问题出在schemeId上。org.eclipse.ui.defaultAcceleratorConfiguration是默认的scheme, 它里面其实包含了大量的快捷键设定,很多可能是你不要的,比如CTRL+F(查找与替换),CTRL+N(新建Wizard)等等。如果发布到产品中,反而画蛇添足。

修正这个问题很容易,就是设置自己的scheme。下面是个例子,

6-15-2015 2-32-59 PM

6-15-2015 2-33-09 PM

别忘要同时修改一下,Key的设定,

6-15-2015 2-34-25 PM

还有如果要让自定义的scheme能生效,要修改一下plugin_customization.ini文件,加入如下配置,

这样,你的Scheme就会生效了。

可能你回问,如果想重用如CTRL+S的快捷键怎么办?那就重用File Save的commandId,如下,

6-15-2015 2-40-14 PM

6-15-2015 2-39-30 PM

这样问题就解决了。

制作本地Eclipse Release P2 Repository

因为某些原因,公司的Build Server都不能访问外网了,所以不能直接从Eclipse的公共服务器上下载开发包。以前Eclipse提供所谓All-in-One package,可现在没有了。还有更方便的做法,可以下载Eclipse的所有Pacakge,生成本地的P2 Repository。

Repository包含metadata和artifact,要分别下载,但可以下载到同一个文件下。

程序运行后,没有UI,只在后台下载。耐心等待下载完成后,程序自动退出,所有的包就在destination里了。如果需要,可以传到Sonatype Nexus做成Virtual Repository (详见在Nexus OSS上建Eclipse p2 repository)。

NOTE

  • 完整的Repository非常大,下载的过程很长,一次下载不完可以分多次下载,重新键入命令即可,下载完成的包都很跳过的。
  • 下载artifact时,会有一个.blobstore文件夹,不要以为它是个临时文件夹,它也是Repository的一部分。

Reference

正则表达式的动态替换

Java中的String类提供了replaceAll和replaceFirst方法,用来识别符合正则表达式的substring,并全局替换和只替换第一个substring。

但有时情况会更复杂一些。比如转换input string中所有的floating number的单位,从厘米转换为米。这时,之前的函数就帮不上忙了,我们要使用Java Regex其他类的更强大功能。

看看下面的例子。

下面是Test cases。

 

Google Guava之Immutable Collection

Guava提供了很多Immutable Collection, 包括Set,Map,List等,使用起来简单方便,看看下面的例子。