【转】自动化测试脚本冻结问题分析与解决方案

2012-10-29  付民 

简介:Rational Functional Tester(RFT)运行过程中偶尔会发生脚本停止响应,既不报错也不继续运行,导致自动化测试项目运行效率低下的问题,本文阐述如何利用 FutureTask 类开辟子线程,设置超时来解决这一问题的思路和具体实现。

  概述

  在用 Rational Functional Tester(RFT)进行自动化测试的过程中,偶尔会发生脚本停止响应的情况:即运行一个自动化测试项目很长时间后想要查看测试结果,却发现由于某个脚本的冻结,测试并没有完成,测试程序卡在某一点,不执行也不报错,使得后面的脚本都没有被运行。出现这种情况的原因可能是 AUT(Application Under Test)出现死锁,浏览器页面停止响应,也可能是 RFT 和 AUT 的兼容性问题,情况复杂,很难避免。脚本冻结发生后,只有先通过进程关闭 AUT,本脚本会报错并 Failed,后面的脚本才会继续运行。这种情况的出现严重影响了测试的效率,给测试人员带来很大的困扰。

  本文通过 java 类 FutureTask 完美解决了上述问题。在本文中,作者将结合自己在测试中遇到的实际案例,简要介绍 FutureTask 的内容,在解决这个问题上的优势,以及具体如何运用 FutureTask 来解决脚本冻结问题。

  脚本冻结问题

  本节以 IE 浏览器冻结为例描述脚本冻结的现象:某个自动化测试脚本的测试对象是嵌入到 Office 软件中的内容管理插件,具体执行过程是先通过 IE 浏览器连接到服务器,修改服务器端的相关配置,然后再打开内容管理软件,对相关功能进行测试。冻结问题出现在通过 IE 设置参数阶段,具体表现是:不再继续执行脚本,也不抛出异常,RFT 用来显示当前操作的 Playback 窗口也不再显示正常执行时的 find 或 sleep 动作,而是显示空白(如图 1 红框中所示),此时的 IE 浏览器已无法通过窗口上的关闭按钮关闭,而只能直接通过进程将其关闭,整个脚本的运行呈现"冻结"的状态,中止执行,后面的脚本也无法运行。

图 1.Playback 界面

  解决思路

  解决这个问题的思路是设置超时,即设置一个时长,如果某一段代码在这个时长内未运行完成,则判断该段代码出现冻结现象。之后做以下处理:关闭 AUT 的进程,并重新运行这段代码。重复上述处理直到该段代码顺利执行完毕。这里之所以要重新运行出现冻结的代码,目的是可以让冻结的脚本重新进行测试,而不是直接 Failed 后执行下一个脚本,如此可以提高测试的准确率和效率。

  由于出现冻结时主线程已卡死,所以要解决这个问题必须开辟子线程,而子线程的作用有两种选择:一是执行计时的程序;二是执行易卡死的代码。选择第一种作用的执行模式很简单,即子线程发现超时后关闭 AUT 进程,使得后面的脚本得以继续执行,但这种方式无法实现前面所说的重新执行冻结脚本设想;第二种方式可以通过循环的方式实现重新执行冻结脚本的设想,但是超时如何判断又成为问题。本文采用了第二种方式,并通过 java 类 FutureTask 完美解决了上述问题。下面就简单介绍一下 FutureTask 的内容,以及它在解决这个问题上的独特优势

  FutureTask 简介及优势

  FutureTask 类是 Future 接口的一个实现,Future 接口提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。主要包含以下五个方法:

  1、boolean cancel(boolean mayInterruptIfRunning):该方法用于取消对任务的执行,如任务已完成或无法取消则返回 false,如果任务正在执行中则通过参数 mayInterr-uptIfRunning 判断是否中断任务执行的线程。

  2、boolean isCancelled():如果在任务完成前成功调用 cancel 方法,则返回 true。

3、boolean isDone():如果任务完成则返回 true,不管是正常完成还会由于异常或被取消而完成

  4、get():阻塞主线程,直到该子线程计算完成,并获取结果

  5、get(long timeout,TimeUnit unit):这是解决冻结问题的核心方法,即等待参数所定的时间后获取结果,如果此时线程还未执行完成,则抛出 TimeoutException。

  FutureTask 也实现了 Runnable 接口,所以可以通过线程池来执行,也可以开辟子线程来执行其中的任务。介绍到这里,FutureTask 用来解决冻结问题的优势已经很明显了:其一,它可以方便的开辟子线程;其二,可以通过它的 get(long timeout,TimeUnit unit) 方法来设置超时。我们需要做的就是将会出现冻结现象的代码作为 FutureTask 的任务,将其交给子线程来执行,然后在主线程中调用 get(long timeout,TimeUnit unit) 方法,并合理设置时长参数。当脚本冻结时,子线程无法在指定时间内完成 FutureTask 的任务,get 方法就会抛出 TimeoutException,主线程捕获这个异常后重新通过 FutureTask 运行出现冻结现象的代码。将此过程放在循环中重复执行,直到 FutureTask 的任务正常完成。因为脚本冻结出现的概率不高,所以一般重复运行两到三次就可以正确执行。

  具体解决方案

  构造 FutureTask 需要一个实现了 callable 接口的类,该类的 call() 方法中的内容就是 FutureTask 需要完成的任务。作者在实例中直接使用了匿名内部类,具体代码如下:

  清单 1. Call 方法

FutureTask<String> future = 
    new FutureTask<String>(new Callable<String>()
{
    public String call() {
    ecmui.factory.Application.login(commonXML.getWPURL(),
    commonXML.getAE_Admin(),commonXML.getCommonUserPW());
     SitePreferences sp = new SitePreferences();
     sp.general().setFileTrackingEnable("Yes");
     sp.appbcs().setDefaultETShowing("Yes");
     sp.set();
    return "";

}});

  其中 call() 方法中的代码就是通过 IE 浏览器设置参数的代码,这里可以替换成任意出现冻结现象的代码。然后是创建线程池,通过子线程执行 FutureTask,代码如下:

  清单 2. 子线程执行 FutureTask

try
    {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     executor.execute(future);
     String result=future.get(1000*60*3, TimeUnit.MILLISECONDS);
    }catch(TimeoutException e)
    {
        future.cancel(true);
        System.out.println(e.toString()+":start workplaceXT again");
        Application.killAppProcess("IEXPLORE");
        sleep(10);
                    }

ExecutorService 用来创建线程池执行 FutureTask。在上面代码中尤其要注意 get 方法的时长设置要合理。在本例中,作者分析了多次未冻结情况下设置参数所需要的时间,发现每次都不超过两分钟,所以为了准确起见,将时长设为三分钟,即如果三分钟内设置参数的代码还未运行完毕,则判断脚本冻结,get 方法抛出 TimeoutException。catch 语句用来捕获 TimeoutException,如果捕获,则依次执行取消任务、输出错误信息、通过进程关闭 IE 浏览器的操作。将上述代码放在循环中就可实现:“运行冻结代码——冻结——关闭冻结程序”,这一过程循环往复,直到成功执行可能出现冻结的代码。完整代码如下:

  清单 3. 完整代码调用

while(true)
{
FutureTask<String> future = new FutureTask<String>(new Callable<String>()
{
public String call() {
  ecmui.factory.Application.login(commonXML.getWPURL(), 
                                       commonXML.getAE_Admin(),
                                       commonXML.getCommonUserPW());
  SitePreferences sp = new SitePreferences();
  sp.general().setFileTrackingEnable("Yes");
  sp.appbcs().setDefaultETShowing("Yes");
  sp.set();
return "";

}});
try
{
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(future);
    String result=future.get(1000*60*3, TimeUnit.MILLISECONDS);
    sleep(3);
    break;

}catch(TimeoutException e)
{
    future.cancel(true);
    Application.killAppProcess("IEXPLORE");
    sleep(10);
    }
    }

  在上述代码中,如果 get 方法抛出超时异常,则是脚本出现冻结,代码直接跳到 catch 语句执行,并不跳出循环;而如果不抛异常,说明参数设置正常且没有出现冻结现象,则顺序执行到 break 语句,跳出循环。

  总结

  文中所述的解决脚本冻结问题的方法有很强的通用性,读者可以将其灵活运用到自己的测试工作中来解决其他类型的脚本冻结问题,提高自动化脚本的测试效率。

454°/4540 人阅读/0 条评论 发表评论

登录 后发表评论