在 Java 单元测试中使用 Truth

Truth 是 Google 的一个开源 Java 库,用于解决 JUnit 测试中不明确的断言(Assertion)问题。

JUnit 的断言通常是这么写的:

上述代码断言获得的 List 为空,当断言失败后,JUnit 抛出如下异常:

看到这种错误信息,一般都得回去看代码,才能知道到底出了什么错,然后再分析原因。而 Truth 的目标就是改变这种状况,让错误信息更有意义。比如在 Truth 中,可以这么写:

当断言失败后,会得到如下错误信息:

这样我们看到错误信息的时候,对出现的问题大致有个印象,比起看到一个 AssertionError 一头雾水的情况要好太多了。

而 Truth 不仅提供了常见类型的断言支持,还可以自定义断言,比如有一个二叉树节点类 Node:

我们要针对它写一个 Truth 的断言支持,可以这样:

然后我们就可以做类似如下的断言:

如果 root 没有左子树,则会显示如下错误:

这样无论是代码,还是错误信息都非常直观,一看就能明白,维护起来也方便。

计算网页中某个元素的位置

由于项目的需要,测试中需要对网页元素进行截图,以确保它看上去没有问题。之前我写过一篇文章介绍过一种方法,先使用 WebDriver 进行全屏截图,然后根据目标元素(DOM Element)所在的位置,再对截下来的图片进行剪裁,保留我们需要的位置即可。

那段代码一直都工作得很好,直到我知道了一个东西:iframe。iframe(普通的 frame 也是一样的,不过 frame 现在不太常见,这里只用 iframe 举例)中的内容被视为一个独立的网页,连 Window 对象也是和它的父级网页分开的。而 WebDriver 中的 WebElement.getLocation()方法只能返回这个 WebElement 和它所在的 Window 的位置关系,它的实现没什么问题,但全屏截图不仅包含了 iframe 的内容,可能也包含了它的父级页面的内容,剪裁的时候需要知道目标元素在截图中的位置。那么问题来了,挖掘机技术哪家强?如何计算一个元素相对于截图的位置?

这个问题还要分类讨论,原因是:Chrome 和 Firefox 中截图的行为是不一样的。Chrome 的截图是当前可见(viewport)的网页内容,比方说,当网页的实际大小超过 Chrome 窗口大小时,根据滚动条的位置不同,窗口中显示的内容不同,Chrome 的截图就是显示出来的内容。于是我们要计算目标元素相对于当前可见内容的位置。而 Firefox 用了一个方法,可以截到整个网页的内容,无视当前窗口大小。于是对于 Firefox 我们要计算元素的绝对位置(Absolute Position)。

获得一个元素的位置,需要用到一个方法:Element.getBoundingClientRect()。这个方法返回这个元素相对于它所处的 Windows 在当前可见内容的位置,用 top、left、right、bottom 四个值来表示。我们只关心其中的 top 和 left,至于剪裁的尺寸,我们可以通过元素本身的长和宽来得到,不需要计算。要计算目标元素对于顶级 Window的位置,我们只需要依次加上它的父级 Window的 top 和 left 即可。代码如下:

以上代码适用于 Chrome ,而在 Firefox 中,我们还需要计算元素的绝对位置。这里需要用到 Window.pageXOffset。pageXOffset,或者 scrollX,表示当前 Window 的横向滚动条滚动的位置,把这个值和上述的 left 相加,即可得到目标元素的横向绝对位置。当然,iframe 也可以特殊处理的:

由于 IE8 不支持 pageXOffset 和 scrollX,于是在 IE8 中需要一些特殊处理,即代码中标注“IE8”的部分。把这两段 Javascript 代码,替换之前文中的 WebElement.getLocation(),即可实现在 iframe 中对特定元素截图。

物理安全是所有安全要素的根本

前一阵有一篇文章很红,链接就不附了,大意是说手机掉了之后,支付宝(或者其它的应用)怎么怎么地不安全,请大家不要过于依赖于支付宝云云。看完之后很多人都觉得手机支付不安全,于是就人心惶惶了。

实际上,那篇文章的出发点就是错误的,它本末倒置了。

先来说一个故事。大学毕业之后,进公司的第一份工作就是软件测试。这个领域在大学里基本没有接触过,于是就和同事老板们各种请教各种讨论。有一天,和老板说起服务器安全的问题,大概就是怎么样的测试才能确保服务器中的数据是安全的,不会被黑客偷走。当然,攻击的方法有很多,从软件到通讯协议,再到底层的操作系统和硬件,当然相应的防御方法也有很多。没多久之后,就深入到了最根本的一个问题,如果服务器被人偷走了怎么办,不仅是里面的内容,整个机器都一起被搬走了,这要怎么防御。

讨论下来的结论是:丝毫没有办法。原因大致是,如果小偷能够接触到运行中的服务器,(在技术条件足够的情况下,)他一定有能力随意读取内存。即使硬盘中的数据再怎么加密,只要读到了内存,所有的加密都起不了作用。

这个情形也同样适用于其它生活中的场合。比如,我的钱包丢了,怎么确保里面的现金的安全?或者我的家门没有锁,怎么防止家里东西不被偷?这一类的假设,大伙一看就会哈哈大笑。完全不合常识嘛。同样,换成是手机,还是一个正在运行中的手机,被偷了之后,同样也没法保证里面数据的安全。数据都不安全了,支付宝显然也就不安全了。这可不能怪支付宝。

当然,选择iPhone之类拆解比较麻烦的手机,总比那些可以轻易拆卸储存卡的手机可来得安全。因为小偷的成本提高了。

如果你非要讨论一下这种情形下,如果减少资金损失的风险,好吧,下面有几条建议:

  • 设置开机密码,并且设置输错N次之后自动清除数据
  • 发现手机丢失之后,先打个电话找找是不是夹在沙发的缝隙里了
  • 如果真的丢了,立即在线清除手机数据,比如使用Apple的Find My iPhone功能
  • 如果清除失败,(比如手机不在线),立即挂失所有的银行卡、停用网上的付款帐户(比如支付宝)
  • 不要在支付宝里放太多的钱,有闲钱还是拿出来玩,或者投资掉比较好

如果你还是觉得不安全,可以把钱转到我的帐户上,支持各种转帐方式,安全可靠,保证钱汇过来再也不会还回去,童叟无欺,量大从优,谁用谁知道……

JUnit中获取测试类和方法的名称

在JUnit的测试中,有时候需要获得所属的类(Class)或者方法(Method)的名称,以方便记录日志什么的。

在JUnit中提供了TestName类来做到这一点,在org.junit.rules中:

虽然TestName只提供了方法的名称,要加上类的名称很容易,只需对TestName稍作修改如下:

在测试用例中的用法是:

大功告成!

软件测试的目标

毕业之后的第一份工作就是软件测试,刚上班不久,就有一位大牛告诫了我一句话:

软件测试的目标不是验证软件的功能是对的,而是验证其功能是符合预期的。

这句看似是文字游戏的话,却蕴含了很多道理。

到目前为止,计算机还不具备思考的能力,它只能按照使用者的指令来运转。计算机没有办法自己推算出一个软件的行为是正确还是错误的,它需要使用者输入额外的数据才能做到这一点。举一个简单的例子,在单元测试(Unit Test)的时候,断言(Assertion)的写法通常是 assertEquals(expected, actual),而不是 assertCorrect(actual)。如果计算机拥有智慧,它就不需要 expected 表达式,只需 assertCorrect。

在日常工作中,预期的行为是需要明确定义的,通常是写在测试计划(Test Plan 或者 Test Spec)中。一份好的测试计划,需要明确定义一系列的软件行为,比如“当XXXX的时候,软件的行为是YYYY”。具体的内容通常来源于产品设计(Design Spec),当然不是照抄,这个以后再说。有了这一系列的定义,测试用例(Test Case)的编写就变得非常简单,也可以避免在后续开发过程中,其他人对测试用例产生的疑问。如果测试计划中只写了“软件可以正确处理XXXX的情况”,由于每个人对“正确”的理解不一样,写出来的测试用例也就各有千秋,软件质量也就不能保证了。

于是,有一些观点认为,专职的测试人员最终会被机器所替代。这显然是不正确的。当机器能自主进行测试的时候,那它也就具备的思考的能力,那是非常可怕的一件事……

iPhone无法连接WiFi的解决方法

iPhone(3GS、4或是4S)在升级到最新的iOS6之后,可能会出现连不上WiFi的问题,最严重的情况是WiFi的开关按钮变成了灰的,完全没法操作,如下图:

据说这种情况在iOS 6中非常多,会造成这种问题的原因,大概是Apple的升级测试(Upgrade Test)没做好,不过这也不能怪Apple,升级测试大概是软件测试过程中最麻烦的一种了,由于升级的方式很多,比如在3GS中从iOS 4升级到iOS 6,或者在4S中从iOS 5升级到iOS 6,每次升级都要有不同的设置,来模拟真实的用户体验,所以需要测试的情形(Scenario)非常多。而在真实的测试过程中,往往只会挑一些常见情形来测试,不会完整地一一枚举。而iOS 6中,估计就是哪个设置出了问题,并且在测试中给乎略了。

好吧,扯了这么多,现在来说说解决方法吧:

1. 还原网络设置。这是最简单,代价最低的尝试,方法是:系统设置->通用->还原->还原网络设置。(如下图)。网络设置完原之后,所有之前的WiFi连接记录都会没有,但不会影响到其它App的使用。

2. 如果“还原网络设置”不起作用,你还可以试试“抹掉所有内容和设置”,这样做之后,iPhone就还原到了最初的设置,就像新装了iOS 6一样,通常来说,这样做可以解决很多升级过程中不可避免的问题,但坏处是,你需要重新安装所有的App。

3. 如果上述方法都不行,那就只能走走歪门邪道了,在Youtube上有个视频,说是把iPhone关机,扔在冰箱里一个小时,再拿出来开机,就恢复正常了。Apple官方论坛中,也有网友证实这样是有效的,只是不是所有人都有效罢了。而且这样有个风险就是在拿出冰箱的时候会起雾,严重的可能会导致内部短路,就再也不能用了……不过现在是冬天,拿到户外操作的效果是一样的。

4. 在Apple论坛中,有人提出过这个一个方式,找一台别人的机器,用它上面的iTunes和出问题的iPhone做同步,什么都不用设置,最基本的同步即可,然后Wi-Fi的设置就回来了……信不信由你……

5. 还有人说,在开机状态下,双击Home键打开应用程序列表,长按图标使之开始抖动,就是可以删除的状态。然后迅速按下电源键直到关机提示出现,并滑动关机。再开机之后,WiFi就恢复了……

如果比较急着用WiFi,可以试试上述方法,如果不急的话,最好的办法是拿到Apple店里去找Genius修,由于iPhone修理比较麻烦,通常Apple都会直接更换机器,在极端情况下,甚至可以换到iPhone 5,相当的合算……

Test Authoring and Execution Framework

很惊喜地看到这个东东被加到SDK里了,于是拿出来写一下。

Test Authoring and Execution Framework,简称TAEF,是Windows团队开发出一个测试框架,主要是给测试人员开发自动化测试所用。我所在的团队大概是国内第一批使用这个框架来做测试的了……

TAEF提供的主要功能有:

  • 提供了开发自动化测试所需要的基类、校验类、元数据定义等,支持C#、C++和一些脚本语言 ( JScript, VBScript, PERLScript, PScript, Ruby, Python);
  • 支持新的Modern App的测试;
  • 支持以不同的账号、不同的权限的测试;
  • 支持跨机器测试;
  • 支持WoA设备;
  • 支持运行时参数和筛选所需的测试用例;
  • 支持测试过程中重启机器;
  • 支持数据驱动测试(Data Driven Test);

TAEF已被集成进Visual Studio 2012中,是Windows SDK 8.0的一部分。

详细的内容以后继续补充……