簡體   English   中英

如何讓 JUnit 測試等待?

[英]How can I make a JUnit test wait?

我有一個 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 (如預期的那樣)。

有什么技巧還是我需要不同的顯示器?

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

Thread.sleep() 在大多數情況下都可以工作,但通常如果您在等待,您實際上是在等待特定條件或狀態發生。 Thread.sleep() 不能保證您等待的任何事情都已實際發生。

例如,如果您正在等待休息請求,它通常會在 5 秒內返回,但如果您將睡眠時間設置為 5 秒,那么您的請求會在 10 秒后返回,您的測試將失敗。

為了解決這個問題,JayWay 有一個名為Awatility的強大實用程序,它非常適合確保在您繼續前進之前發生特定情況。

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

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

https://github.com/jayway/awaitility

如果您的靜態代碼分析器(如 SonarQube)抱怨,但您想不出其他方法,而不是睡眠,您可以嘗試使用類似的 hack: Awaitility.await().pollDelay(Durations.ONE_SECOND).until(() -> true); 它在概念上是不正確的,但它與Thread.sleep(1000)相同。

當然,最好的方法是使用適當的條件傳遞一個 Callable,而不是我擁有的true

https://github.com/awaitility/awaitility

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

這個庫為時間單位提供了更清晰的解釋。 您可以使用“小時”/“分鍾”/“秒”。

如果絕對必須在測試中產生延遲, CountDownLatch是一個簡單的解決方案。 在您的測試類中聲明:

private final CountDownLatch waiter = new CountDownLatch(1);

並在需要的測試中:

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

也許沒必要說,但請記住,您應該保持較小的等待時間,而不是累積等待太多地方。

您還可以使用此處解釋的CountDownLatch對象。

有一個普遍的問題:很難模擬時間。 此外,將長時間運行/等待的代碼放在單元測試中是非常糟糕的做法。

因此,為了使調度 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引用和良好的時間同步策略來實現它。

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或其他方法來確保在斷言發生之前滿足異步先決條件。

在測試中使用 Thread.sleep 不是一個好習慣。 它會創建脆弱的測試,這些測試可能會因環境(“通過我的機器!”)或負載而無法預測地失敗。 不要依賴時間(使用模擬)或使用諸如 Awaitility 之類的庫進行異步測試。

Dependency : testImplementation 'org.awaitility:awaitility:3.0.0'

await().pollInterval(Duration.FIVE_SECONDS).atLeast(Duration.FIVE_SECONDS).atMost(Duration.FIVE_SECONDS).untilAsserted(() -> {
      // your assertion
    });

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM