120

我有一个 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(如预期的那样)。

有什么技巧还是我需要不同的显示器?

4

8 回答 8

119

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

于 2013-04-10T23:54:17.403 回答
101

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

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

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

它也有一个很好的流利的 api

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

https://github.com/jayway/awaitility

于 2015-05-27T02:07:44.617 回答
29

您可以使用内部使用 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"));
}

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

于 2019-06-05T21:55:09.987 回答
24

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

当然,最好的方法是在您的适当条件下传递一个 Callable,而不是true我拥有的 .

https://github.com/awaitility/awaitility

于 2018-08-02T10:01:17.573 回答
15

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

private final CountDownLatch waiter = new CountDownLatch(1);

并在需要的测试中:

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

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

于 2020-11-02T12:16:13.737 回答
6

您也可以使用此处CountDownLatch解释的对象。

于 2016-02-02T21:12:45.157 回答
2

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

因此,为了使调度 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引用和良好的时间同步策略来实现它。

于 2019-07-02T13:37:20.383 回答
0

Mockito(已经通过 Spring Boot 项目的传递依赖项提供)有几种方法来等待异步事件,分别是条件发生。

目前对我们非常有效的一个简单模式是:

// ARRANGE – instantiate Mocks, setup test conditions

// ACT – the action to test, followed by:
Mockito.verify(myMockOrSpy, timeout(5000).atLeastOnce()).delayedStuff();
// further execution paused until `delayedStuff()` is called – or fails after timeout

// ASSERT – assertThat(...)

@fernando-cejas在这篇文章中描述了两个稍微复杂但更复杂的


我对此处给出的当前最佳答案的紧急建议:您希望您的测试

  1. 尽快完成
  2. 具有一致的结果,独立于测试环境(非“片状”)

...所以不要愚蠢Thread.sleep()地在测试代码中使用。

相反,让您的生产代码使用依赖注入(或者,有点“脏”,公开一些可模拟/可监视的方法),然后使用 Mockito、AwaitlyConcurrentUnit或其他方法来确保在断言发生之前满足异步先决条件。

于 2022-02-15T21:44:36.520 回答