【问题标题】:How to handle a wait() method call in JUnit test case?如何处理 JUnit 测试用例中的 wait() 方法调用?
【发布时间】:2017-04-03 06:23:47
【问题描述】:

我有一个方法有另一个方法调用,它的定义有一个 SomeIntegerObject.wait() 调用。现在,当我运行我的测试用例时,由于这种等待方法,测试用例一直在等待并且不执行。

我尝试在 @Test 注释处添加超时,但这会导致“测试超时”之类的异常。

测试方法:

private PastingResult result;
public PastingResult pasteImages(IIOImage[] images, byte pasteProcessType){
     result = new PastingResult();
     _reprocessingProvider.setListLogicHandler(this);
     //--some more method calls--
    waitForResult(); //causes my test case to wait forever and pauses execution
    return result;
}

测试用例:

@Test
    public void testPasteImagesIIOImageArrayByte() throws Exception 
    {
        ReprocessManager _reprocessingProvider1=Mockito.mock(ReprocessManager.class);
        Whitebox.setInternalState(imagePasterIfImpl, "_reprocessingProvider", _reprocessingProvider1);

        IIOImage[] images=new IIOImage[]{Mockito.mock(IIOImage.class),Mockito.mock(IIOImage.class)};
        byte pasteProcessType = 02;

        PastingResult result= imagePasterIfImpl.pasteImages(images, pasteProcessType);  
        Mockito.verify(_reprocessingProvider1).setListLogicHandler(imagePasterIfImpl);
        System.out.println(result+"is the result");
    }

waitForResult();的定义

private Integer processingWaitMonitor = new Integer(0);
private void waitForResult() {
       // synchronize to wait on the Monitor..
       synchronized(processingWaitMonitor) {
           try {
               processingWaitMonitor.wait();
           }
           catch(Exception e) {
               System.out.println("Exception during Wait !"+e);
           }
       }
   }

【问题讨论】:

  • 那么,您希望/希望在您的情况下做什么?即使代码没有等待,也让测试成功?
  • 我想验证测试方法中发生的所有方法调用,但是测试用例本身并没有执行,它只是一直在等待
  • 无论如何,好问题!
  • @hushie 你是什么意思“我想验证所有方法调用?”,测试用例如何失败并出现“测试超时”?
  • @AmberBeriwal 我正在为上面提到的方法编写一个测试用例,因此在该方法中有一些方法调用,当我验证这些调用时,waitForResult(); 调用会停止执行我的测试用例。当我刚刚尝试在 @Test 注释处添加 1000 毫秒的超时时发生了“测试超时异常”,因此我删除了超时。

标签: java multithreading unit-testing junit mockito


【解决方案1】:

这里有两个答案:

  • 在第一层:如果您需要控制被测对象“内部”的某些东西;您可以求助于 Mockito Spys 或 PowerMock 或 JMockit;因为这些框架允许在这些方面“模拟”(也就是获得控制权)。但在大多数情况下,这并不是真正的好习惯。如果有的话,您应该将所需的功能“外包”到另一个类;然后您使用依赖注入来为您的测试设置提供该类的 mocked 对象。而不是真正的等待,您只需在一个什么都不做的模拟对象上调用一些等待方法。
  • 除此之外:您可以退后一步,考虑重新设计您的整个方法。你看,使用诸如等待/通知之类的“低级”原语不再是的做法了。有ExecutorService、Futures、Promises 等抽象。关键是:使用 ExecutorService,您可能能够重写代码以使用 Same-Thread-Executor-Service 进行测试。含义:可以重写多线程代码,以便仅通过为测试提供不同的执行器,所有事情都发生在单个线程上。无需再等待,获得有保证的可靠结果。

关于您的评论;你的代码说:

private Integer processingWaitMonitor = new Integer(0);
private void waitForResult() {

含义:你在你的测试类中有一个字段+私有方法。假设你创建了

public interface<E> WaitService {
   public E waitForResult();
}

或类似的东西。进而;而不是在你的生产类中拥有一个锁对象和那个等待方法,你只在那里保留那个服务的“一些”实例。但是,当然,这又需要您更改生产代码。

如果您真的无法更改该代码(这有点可惜 - 当您无法更改代码时,测试代码有什么意义?)...那么 Mockito/PowerMock/JMockit 是您唯一的选择在那里。

【讨论】:

  • 很遗憾我无法修改生产代码,但是您提出的观点非常有用。你能详细说明你的第一点吗?我不明白您所说的将所需功能“外包”到另一个类是什么意思。
  • 哦,好吧,现在我明白了你刚刚给出的例子,所以除了使用 Mockito/PowerMock/JMockit 之外我没有其他方法?
  • 好吧。我已经概述了这些选项。如果更改代码不是一种选择,您需要通过转向我所说的“框架锤击”来研究解决该不可测试的设计。但请理解:这实际上可能会奏效,但这既不容易,也不健壮。这样的解决方案很容易崩溃;而且它们不会增加太多价值。请记住,您想要测试代码是有原因的。
  • 转向 PowerMock,Spies ......你有一个问题,而不是修复它(通过更改生产代码),你使用框架技巧“锤击”它。比如:你有一幅画要挂在墙上;但是你只有一把锤子和一个螺丝钉,没有螺丝刀。但您的解决方案不是购买螺丝刀,而是将螺丝“锤”入墙上,将绘画固定在墙上。有效,但不是一个真正的好策略。
  • 好的,明白了,照你说的,因为我不能用螺丝刀把画挂在墙上,所以我必须用锤子。
【解决方案2】:

正在等待的模块,将其放入junit测试用例的新线程中。间隔后,检查线程是否完成。设置间隔的最大超时。超时发生后,检查线程完成情况并决定是通过还是失败。

例如:

private static final int MAX_WAIT_TIME = 20000; //total time out in milliseconds
private static final int WAIT_INTERVAL = 1000; //wait interval till MAX_WAIT_TIME

@Test
public void testPasteImagesIIOImageArrayByte() throws Exception 
{
  Thread t = new MyThread();
  t.start();    
  int totalWaitTime = 0;
  while ((!t.isCompleted()) && totalWaitTime <= MAX_WAIT_TIME) {
    try {
        totalWaitTime += WAIT_INTERVAL;
        Thread.sleep(WAIT_INTERVAL);
    } catch (java.lang.InterruptedException e) {
        //LOG OR THROW
    }
    if(!t.isCompleted()){
      if(t.hasExceptionOccurred()){
        //fail the case
      }else{
        //take your decision here, if you want to pass it or fail it
      }
    }
  }
}

class MyThead extends Thread{
  private volatile boolean isCompleted = false;
  private volatile Throwable exception = null;
  private volatile boolean hasExceptionOccured = false;

  public void run(){
    try{
        ReprocessManager _reprocessingProvider1=Mockito.mock(ReprocessManager.class);
        Whitebox.setInternalState(imagePasterIfImpl, "_reprocessingProvider", _reprocessingProvider1);

        IIOImage[] images=new IIOImage[]{Mockito.mock(IIOImage.class),Mockito.mock(IIOImage.class)};
        byte pasteProcessType = 02;

        PastingResult result= imagePasterIfImpl.pasteImages(images, pasteProcessType);  
        Mockito.verify(_reprocessingProvider1).setListLogicHandler(imagePasterIfImpl);
        System.out.println(result+"is the result");

        synchronized(this){
          isCompleted = true;
        }
    }catch(Throwable ex){
        synchronized(this){
            exception = ex;
            hasExceptionOccured = true;
        }
    }
  }

  public synchronized boolean isCompleted(){
    return isCompleted;
  }

  public synchronized boolean hasExceptionOccurred(){
    return hasExceptionOccured;
  }

  public synchronized Throwable getExceptionOccurred(){
    return exception;
  }
}

【讨论】:

  • 我对线程概念有点生疏,所以请原谅这些愚蠢的问题,但我不想创建一个名为MyThread 的单独类。有没有办法在不创建单独的类的情况下做到这一点?
  • 无论如何都需要一个新线程。如果您不想自己进入线程的事情,那么您可以使用 GhostCat 建议的线程抽象层。
  • 保证上面的示例可以正常工作,您必须设置一个超时时间。事实上,如果你设置了超时,Junit 也会为你做同样的事情。如果你自己做,你可以控制是否通过或失败。
  • 好吧,我知道它肯定会起作用。但是创建另一个类,然后创建另一组方法来做这件事看起来有点乏味。当有人通过我的测试用例时,我想找到一种不那么乏味且更容易理解的方法。
  • @hushie 我认为 JUnit TimeOutException 将是所有可用选项中最简单、最紧凑的......
猜你喜欢
  • 2014-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-29
相关资源
最近更新 更多