用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,有机会现研究一下。😄

聊聊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自己的加载器加载的,非常灵活,可以实现非常牛逼的功能!

 

正则表达式的动态替换

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

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

看看下面的例子。

下面是Test cases。

 

Google Guava之Immutable Collection

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

 

Google Guava之Ordering

Guava提供的Ordering比Comparator强大很多,使用起来非常灵活。下面是个小例子。

 

 

Class.cast() vs. cast operator

A co-worker told me Class.cast() is better than cast opertor as it’s safer to handle null reference. Really?

Let’s take a look at the following code snippet. As you can see, basically, there is no difference. Both test cases can pass.

In the function of cast(), it does null and type check but still uses cast operator to cast object.

What’s the difference in byte code? See the source code and byte code (compiled by Java 7) of a simple test case.

Note: Internally, line index starts from 0.

For L1: Instruction of “CHECKCAST” will be invoked to do type casting. In JVM specification, you can find all details about it. Basically, it behaves the same as we expects. See more details here.

For L2: Function cast() will be invoke first and later “CHECKCAST” still needed. Why? I think Java compiler plays a trick. cast() is a generic function, which should return an object in T type. But in runtime, information of type will be erased. So compiler need to put an extra instruction here to do type casting, which should be the same for other generic functions. Actually, in cast(), “CHECKCAST” is not really invoked.

In that case, way to use case() has worse performance.

See a simple performance test.

And the difference is BIG.

Conclusion

I think in most cases, cast operator is safe and simple. But in some cases, when you only have Class object, such as some generic function/class, cast() is also a good choice.

When sharing SharedPreference among activities, pay more attention!

Generally, we update SharedPreference by its editor;

Then we use putXXX to update the value in it.

Finally, commit the change to save it to file. We often do it in onStop() or onDestroy()

It’s OK in one activity. But if multiple activities get the same preference, you need pay more attention. Since after committing, the previous settings, especially updated by other activities, may be lost. You may need a class to do updating/committing if you only want one preference. Or you can have multiple SharedPreference.

Add watchpoint for variables in Eclipse

Sometimes, in debugging, we want program stopped when some variables are accessed or modified. To set traditional breakpoints for this case, you have to spend a lot of time to find all places to access or modify variables. It’s even harder when variables are passed to other object or the derivation hierarchy is complicated. In this case, watchpoint is a good helper.

In Eclipse, it’s quite easy to set watchpoint. In “Outline” view, you can right click on the variable you want to want and select “Toggle Watchpoint“. 2014-11-06_13-21-43

Then you can find a new watchpoint in “Breakpoints” view. You can see the icons of breakpoint and watchpoint are different.

2014-11-06_13-24-02

By default, watchpoint will be hit when accessing or modifying the watched variable. To change it, we can simply right-click the watchpoing and select “Breakpoint Properties…“.2014-11-06_13-27-11