【问题标题】:How can I make a JUnit test wait?如何让 JUnit 测试等待?
【发布时间】:2013-04-03 01:02:32
【问题描述】:

我有一个 JUnit 测试,我想同步等待一段时间。我的 JUnit 测试如下所示:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    // WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExipred("foo"));
}

我尝试了Thread.currentThread().wait(),但它抛出了 IllegalMonitorStateException(如预期的那样)。

有什么窍门还是我需要不同的显示器?

【问题讨论】:

    标签: java junit thread-safety


    【解决方案1】:

    有一个普遍的问题:很难模拟时间。此外,将长时间运行/等待的代码放在单元测试中是非常糟糕的做法。

    因此,为了使调度 API 可测试,我使用了一个带有真实和模拟实现的接口,如下所示:

    public interface Clock {
        
        public long getCurrentMillis();
        
        public void sleep(long millis) throws InterruptedException;
        
    }
    
    public static class SystemClock implements Clock {
    
        @Override
        public long getCurrentMillis() {
            return System.currentTimeMillis();
        }
    
        @Override
        public void sleep(long millis) throws InterruptedException {
            Thread.sleep(millis);
        }
        
    }
    
    public static class MockClock implements Clock {
    
        private final AtomicLong currentTime = new AtomicLong(0);
        
    
        public MockClock() {
            this(System.currentTimeMillis());
        }
        
        public MockClock(long currentTime) {
            this.currentTime.set(currentTime);
        }
        
    
        @Override
        public long getCurrentMillis() {
            return currentTime.addAndGet(5);
        }
    
        @Override
        public void sleep(long millis) {
            currentTime.addAndGet(millis);
        }
        
    }
    

    有了这个,你可以在你的测试中模仿时间:

    @Test
    public void testExpiration() {
        MockClock clock = new MockClock();
        SomeCacheObject sco = new SomeCacheObject();
        sco.putWithExpiration("foo", 1000);
        clock.sleep(2000) // wait for 2 seconds
        assertNull(sco.getIfNotExpired("foo"));
    }
    

    Clock 的高级多线程模拟当然要复杂得多,但您可以使用 ThreadLocal 引用和良好的时间同步策略等来实现。

    【讨论】:

      【解决方案2】:

      如果绝对必须在测试中产生延迟CountDownLatch 是一个简单的解决方案。在你的测试类中声明:

      private final CountDownLatch waiter = new CountDownLatch(1);
      

      并在需要的地方进行测试:

      waiter.await(1000 * 1000, TimeUnit.NANOSECONDS); // 1ms
      

      也许没必要说,但请记住,您应该保持较小的等待时间,而不是累积等待太多地方。

      【讨论】:

      • IntelliJ 不幸抱怨,因为await 的结果被忽略了。
      • @Druckles 你在做像var result = waiter.await(.. 这样的任务吗?在这种情况下,不要成功。如果不这样做,那么警告很奇怪。可能有一些“checkstyle”规则总是需要赋值,但几乎不需要。
      • @pirho 我可以在测试中多次调用waiter.await() 吗?
      • @linuxNoob 为什么不呢。对我来说,当我在两个单独的测试中多次使用它时,即使是 Junit5 并行测试运行似乎也适用于同一类级别的静态 CountDownLatch
      【解决方案3】:

      如果您的静态代码分析器(如 SonarQube)抱怨,但您想不出其他方法,而不是睡觉,您可以尝试以下 hack: Awaitility.await().pollDelay(Durations.ONE_SECOND).until(() -> true); 它在概念上是不正确的,但它与Thread.sleep(1000) 相同。

      当然,最好的方法是传递具有适当条件的 Callable,而不是 true,我有。

      https://github.com/awaitility/awaitility

      【讨论】:

      • @Jitendra:请注意,Awaitility 曾经有 Duration 类,但从第 4 版开始,他们将其重命名为 Durations
      • 一个警告,如果您希望轮询延迟为 10 秒或更长,请务必将超时时间增加到 10 秒以上,因为这是默认超时时间,等待性会抱怨超时时间小于投票延迟。
      【解决方案4】:

      您可以使用内部使用 Thread.sleep 的 java.util.concurrent.TimeUnit 库。语法应该是这样的:

      @Test
      public void testExipres(){
          SomeCacheObject sco = new SomeCacheObject();
          sco.putWithExipration("foo", 1000);
      
          TimeUnit.MINUTES.sleep(2);
      
          assertNull(sco.getIfNotExipred("foo"));
      }
      

      这个库为时间单位提供了更清晰的解释。您可以使用“小时”/“分钟”/“秒”。

      【讨论】:

      • 如果我从测试中启动工作线程,sleep() 会影响工作线程吗?
      • Sonar 没有抱怨 TimeUnit.SECONDS.sleep(2);,但根本问题是相同的:您阻塞了线程,它在您的测试服务器上的行为可能不同(“但在我的机器上工作”(TM ))
      【解决方案5】:

      您也可以使用CountDownLatch 对象,如here 解释的那样。

      【讨论】:

        【解决方案6】:

        Thread.sleep() 在大多数情况下都可以工作,但通常如果您在等待,您实际上是在等待特定条件或状态发生。 Thread.sleep() 不能保证您等待的任何事情都已实际发生。

        例如,如果您正在等待休息请求,它通常会在 5 秒内返回,但如果您将睡眠时间设置为 5 秒,那么您的请求会在 10 秒后返回,您的测试将失败。

        为了解决这个问题,JayWay 有一个名为 Awatility 的强大实用程序,它非常适合确保在您继续之前发生特定情况。

        它也有一个不错的流利的 api

        await().until(() -> 
        {
            return yourConditionIsMet();
        });  
        

        https://github.com/jayway/awaitility

        【讨论】:

        • 我无法在 Android Studio 中进行编译。
        • 你是否将这一行添加到你的 gradle 文件中:compile 'org.awaitility:awaitility:3.0.0' ?
        • 没有。您可以在要等待特定条件的测试用例中添加它
        • 这是一个非常棒的实用程序!还有一个 kotlin dsl。
        【解决方案7】:

        Thread.sleep(2000); 怎么样? :)

        【讨论】:

        • 如果你使用像 sonarqube 这样的代码分析工具,他们会抱怨 Thread.sleep 说类似 在测试中使用 Thread.sleep 通常是个坏主意。它会创建脆弱的测试,这些测试可能会因环境(“通过我的机器!”)或负载而无法预测地失败。不要依赖时间(使用模拟)或使用诸如 Awaitility 之类的库进行异步测试。
        • 这个答案应该被删除,并被认为是有害的。下面一个更好的答案是stackoverflow.com/a/35163873/1229735
        • 为避免“Thread.sleep 不应在测试中使用”声纳代码气味,请使用此答案中的解决方案stackoverflow.com/a/51650781/1167954
        • SonarQube 不推荐 Thread.sleep()。这个答案可以完成工作,但根据编码标准它会产生误导。
        猜你喜欢
        • 2018-08-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-19
        • 1970-01-01
        • 2020-07-11
        相关资源
        最近更新 更多