zoukankan      html  css  js  c++  java
  • "Timeout"在测试框架里是如何被实现的

            今天组里的小伙伴问了我一个问题:“我这里有一个底层驱动的接口,我想在测试它的时候加上超时限制,时间一过就fail掉它,执行后面的测试用例。怎么办到呢?”。我问:“它自己没有超时响应的机制么? 超时抛exception或者返回错误提示什么的?”,小伙伴回答是“好像没有。” 我接着问: “这个接口是做什么的,是核心交易么?” “算是吧,调用还挺频繁的。”小伙伴回答。“那这个接口决不能让它通过测试啊!”我大声回答,旁边n人侧目。“好吧。那我如何实现超时fail呢?” 小伙伴继续问。。。“呃。。。让我慢慢道来。”

          超时处理其实是编程过程中经常要面对的问题。在我们调用某个函数的时候,调用方把控制权交给了被调用方,但被调用方很多时候是不可控的,如果被调用方长时间不给调用方返回结果,调用方就要想别的办法,不然就hang死在那里了。这就是超时处理最初始的需求,它的本质是要求把同步调用变成异步调用。把同步变异步其实是个较大的话题,不同的高级语言,框架,甚至操作系统都进行了各式各样的封装,提供了各式各样的接口,十分精彩,但这并不是今天的重点,因此不会展开说了。下面举几个例子来看看“timeout”是怎么实现的。先拿JAVA语言来说吧:

         首先要说明的是,在单线程下,不借助一些特殊工具,“超时处理”是很难实现的,请见下面的代码:

    RemoteServer itest = new RemoteServer()
    String result = itest.callRemote()  //callRemote()是一个远端接口

    如果callRemote()方法永远不给返回值,那程序就一直停留 result = itest.callRemote() 这一行不往下走了。

    如何实现下面语法中想要的结果呢,如果callRemote()永远不会抛出TimeoutException的话?

    try{
    RemoteServer itest = new RemoteServer()
         result = itest.callRemote()
    }catch(TimeoutException e) 
    { e.printStackTrace() }

    多线程?这是个好主意!你最初的想法可能是:让一个子线程在调用前开始计时,如果超时了通知主线程。如果是我,我会一般想到两种通知的方式:一种是抛出异常,让主线程捕获,另一种是Listener的方式实现callback。

    如果你使用第一种抛异常的方式,见如下代码:

    public class TimeCount implements Runnable {
        private long timeOut;
        private long beginTime;
        public  TimeCount(long timeOut,long beginTime){
            this.timeOut = timeOut;
            this.beginTime = beginTime;
        }
    
        public void run() throws TimeOutException{
            while(true) {
                if((System.currentTimeMillis()-beginTime)>timeOut){
                    throw new TimeOutException("Timeout!");
                }
            }
        }
    }

    恭喜你,Java不允许run() 方法向上抛出异常。就算你@override 它也不行。这也是JDK早期的线程模型一个重要槽点。

    OK,那我只能使用listener的方式了。

    Private XXListener lstr;  
    
      public void run() throws TimeOutException{
            while(true) {
                if((System.currentTimeMillis()-beginTime)>timeOut){
                    lstr.notify("Time out!");    
                }
            }
        }

    但是这样如果没有现成的Listener,你就要去实现它,还是很复杂的(有兴趣可以看看这篇文章),同时,对被测类产生了一定入侵。可见,上面两种方法都不是什么好方法。那么有没有什么较好的方法呢?Java其实对多任务调度实现了非常好的封装在(java.util.concurrent包里),我们可以使用下面代码方便的实现异步。

    先看一下被测类:

    import java.util.*;
    public class TimeOutCall {
            public String CallWithTimeOut( long timeSetting ) //被测物方法,输入参数可以设置多长时间返回。
           {
                   long startTime = System.currentTimeMillis ();
                   while(true )
                  {
                          if(System.currentTimeMillis() - startTime < timeSetting )
                                continue;
                          else
                                return "Result returned!" ;
                  }
           }
    }


     
     
     

    再看一下实现超时的调用类:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Callable;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.TimeoutException;
    public class CallTest {
        public static   void main(String [] args)
        {
            ExecutorService service = Executors. newSingleThreadExecutor();
            Callable<String> callable  = new Callable<String>(){ //实现Callable接口的匿名类
                public String call() throws Exception{
                    TimeOutCall toc = new TimeOutCall();
                    return toc.CallWithTimeOut(3000);  //3秒返回结果
                }
            };
            Future<String> future = service.submit( callable);
            service.shutdown();
            try {
                if(service.awaitTermination (1000, TimeUnit.MILLISECONDS) == false)//等待1秒后抛出异常。
                    throw new TimeoutException();
                System. out.println(future .get());
    
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    上述代码中我们创建了一个ExecutorService类,并且将一个Callable接口类型的作业提交给了类,而Future可以异步的等待Callable作业的执行结果。想查看作业执行时间的话,ExecutorService提供了一个相当方便的方法 awaitTermination来检测是否作业在开始后一段时间还在继续执行,通过对方法第一个参数的设置,我们很容易能够设置超时的时限。

    事实上,Junit也是用类似的方法实现超时检测的,在Junit中,我们可以方便的使用Annotation给一个测试方法加入超时检测:

    @Test(timeout =1000)
    public void testXXX(){
    ...
    }

    而它在代码中对方法超时的实现核心代码如下(org.junit.internal.runners.MethodRoadie.java中):

     private void runWithTimeout(final long timeout) {
            runBeforesThenTestThenAfters(new Runnable() {
    
                public void run() {
                    ExecutorService service = Executors.newSingleThreadExecutor();
                    Callable<Object> callable = new Callable<Object>() {
                        public Object call() throws Exception {
                            runTestMethod();
                            return null;
                        }
                    };
                    Future<Object> result = service.submit(callable);
                    service.shutdown();
                    try {
                        boolean terminated = service.awaitTermination(timeout,
                                TimeUnit.MILLISECONDS);
                        if (!terminated) {
                            service.shutdownNow();
                        }
                        result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation
                    } catch (TimeoutException e) {
                        addFailure(new TestTimedOutException(timeout, TimeUnit.MILLISECONDS));
                    } catch (Exception e) {
                        addFailure(e);
                    }
                }
            });
        }

    不过,如果你在Junit中使用了@BeforeClass 和@AfterClass,并且有多个测试用例都必须检测超时,则建议使用Rules来设置整体超时时间。

    大家也可以参考StackOverFlow上的这个链接,看一下讨论过程,相信会有一个更加深入的理解:

    http://stackoverflow.com/questions/2758612/executorservice-that-interrupts-tasks-after-a-timeout

    上文说过,不同语言,不同操作系统,timeout实现起来很不一样。比如python,如果在UnixLike系统下,可以用python内置的signal包来方便的实现超时,我们来看下面的例子:

    import signal, os
    
    def handler(signum, frame): #产生超时后调用。
        print 'Signal handler called with signal', signum
        raise IOError("Couldn't open device!")
    
    # Set the signal handler and a 5-second alarm
    signal.signal(signal.SIGALRM, handler) #指定handler
    signal.alarm(5)  #设置5秒超时
    
    # This open() may hang indefinitely
    fd = os.open('/dev/ttyS0', os.O_RDWR)
    
    signal.alarm(0)

    在使用的时候,我们可以很方便的把它也封装成Decorator(等同于Java的Annotation)

    但是,在Windows下,就没有那么幸运了,因为signal包直接使用了unixlike操作系统的信号量机制,这时候实现超时就会相对麻烦一些。什么?多线程?答案又一次对了。我们可以使用multiprocessing 

    包中的函数来实现超时检测。下面是粗略的代码实现(真的是粗略的实现)要想深入了解,请看python多线程的在线文档

    __author__ = 'lucasliu'
    from multiprocessing import Process
    import time
    
    def timefucntion(sleeptime ):
        time.sleep(sleeptime)
        print 'timefuction returned after', sleeptime,'seconds'
    
    if __name__ == '__main__':
        p = Process(target=timefucntion,args=(3,))
    
        starttime = time.time()
        timeoutsetting = 2
    
        p.start()
        p.join(timeout=timeoutsetting)
        if abs(time.time() - starttime - timeoutsetting)<0.1:
            print 'timeout'
            p.terminate()

          那么在常见python的测试框架里,timeout又是如何实现的呢?有点儿遗憾,python的内置单测框架unittest不支持超时检测。因此我们来看Robotframework是如何实现超时的:一句话,就是根据不同的操作系统,用不同的方法实现超时,并在框架上层统一起来,对用户透明。源码量稍微有点儿大,就不在这里搬运了。有兴趣可以去看Robotframework的 robot.running.timeouts包里的代码,看完一定会有收获。

         至于ruby,就封装的更好了。直接有一个timeout库,引入后可以极为方便的实现timeout,如下面代码:如果do sothing的时间超过了 timeoutsetting的设置就会抛出异常。所以,用ruby的同学相对幸福一些。

    require 'timeout'
    
    begin
    timeout(timeoutsetting ){
        do something
    }
    rescue Exception
      puts "timeout"
    ensure
      puts "finish"
    end
  • 相关阅读:
    网络编程IO模型
    网络编程四层模型
    签到赛--我打的
    签到赛--ipip
    初见RCE(远程命令/代码执行漏洞)
    [ACTF2020 新生赛]Include
    文件上传+[SUCTF 2019]CheckIn
    古典密码,背包密码,RSA
    第八章小结
    第七章小结
  • 原文地址:https://www.cnblogs.com/skytraveler/p/4072511.html
Copyright © 2011-2022 走看看