簡體   English   中英

如何在單元測試中避免 Thread.sleep?

[英]How to avoid Thread.sleep in Unit tests?

假設我有以下應該測試的方法:

@Autowired
private RoutingService routingservice;

public void methodToBeTested() {
    Object objectToRoute = initializeObjectToRoute();
    if (someConditions) {
         routingService.routeInOneWay(objectToRoute);
    } else {
         routingService.routeInAnotherWay(objectToRoute);
    }
}

在這種情況下, RoutingService在單獨的線程中運行,因此在它的構造函數中,我們有以下內容:

Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();

問題是, RoutingService改變的狀態objectToRoute而這正是我要檢查,但這並不會馬上因此測試失敗發生。 但是,如果我添加Thread.sleep()則它可以工作,但據我所知,這是不好的做法。

在這種情況下如何避免Thread.sleep()

如果你的方法測試[單元測試] methodToBeTested ,你應該簡單地模仿routingservice
您不應該測試methodToBeTested調用的任何方法。

然而,這聽起來像你想測試的RoutingService (你說:“問題是, RoutingService改變的狀態objectToRoute而這正是我要檢查什么”)。

要測試RoutingService方法,您應該為這些方法編寫單獨的單元測試。

我建議等待同步異步測試。 例如,假設您有一個在一些線程操作后設置的結果對象,並且您想測試它。 你寫這樣的語句:

 await()
.atMost(100, TimeUnit.SECONDS)
.untilAsserted(() -> assertNotNull(resultObject.getResult()));

它最多等待 100 秒,直到斷言得到滿足。 例如,如果 getResult() 在 0 到 100 秒之間返回不為空的內容,則執行將繼續,這與 Thread.sleep 不同,它會在給定時間內保持執行,無論結果是否存在。

您可以模擬objectToRoute來設置CompletableFuture的值,然后在斷言中調用get 這將等到值被設置后再繼續。 然后設置超時@Test(timeout=5000)以防從未設置該值。

這樣做的優點是測試不會等待超過必要的時間,並且由於時間太短而更難失敗,因為您可以使超時比正常情況大得多。

這取決於。 正如本格林在他的評論中所說,睡眠在測試中是危險的,因為它可以隱藏競爭條件。 知道整體設計是否包含這樣的競爭條件,即可以在路線准備好之前使用服務。 確實如此,您應該在代碼中修復它,例如通過測試就緒條件,並在您的測試類中對其進行相同的測試。

如果你知道它不會發生,你應該在你的主代碼你的測試類中記錄它。 這將是一個完美的睡眠理由。

(我假設這是用於集成測試 - 對於單元測試模擬應該足夠了,正如您在其他答案中所說的那樣)

使用 Object.wait()

我們已經使用了一些從目錄或 .ZIP 檔案中獲取文件的異步進程。 我們在其他地方使用文件的內容,而異步文件繼續讀取。

我們來測試它們的方法是在通信對象上使用wait() ——在我們的例子中它是一個隊列——,所以只要有一個新文件可供使用,異步進程就會queue.notifyAll()並且繼續工作。 另一方面,消費者將一次處理一個項目,直到隊列為空,然后queue.wait()等待更多。 我們確實使用了一個通過隊列傳遞的特殊對象來指示沒有更多的項目要處理。

在您的情況下,我想您要檢查的對象objectToRoute,並未真正在您要測試的方法中創建。 您可以在測試中對其進行wait() ,並在要測試的方法methodToBeTested.對其進行notifyAll() methodToBeTested. 我知道這會在您的生產代碼庫中引入額外的代碼行,但是沒有人等待它,它應該會產生無害的結果。 它最終會是這樣的:

public void methodToBeTested(Object objectToRoute) {
    if (someConditions) {
         routingService.routeInOneWay(objectToRoute);
    } else {
         routingService.routeInAnotherWay(objectToRoute);
    }
    synchronized(objectToRoute) {
        objectToRoute.notifyAll();
    }
}

在你的測試類中會有類似的東西:

@Test
public void testMethodToBeTested() throws InterruptedException {
    Object objectToRoute = initializeObjectToRoute();
    methodToBeTested(objectToRoute);
    synchronized (objectToRoute) {
        objectToRoute.wait();
    }
    verifyConditionsAfterRouting(objectToRoute);
}

我知道在這個簡單的例子中沒有太大意義,因為示例系統不是多線程的。 我假設在routeInOneWayrouteInAnotherWay方法中添加了多線程扭曲; 因此,那些是調用notifyAll()方法的那些。

作為指明我們解決方案方向的示例代碼,這里有一些代碼片段。

在異步工作者端,或生產者:

while(files.hasNext(){
   queue.add(files.next());
   synchronized (outputQueue) {
       queue.notifyAll()
   }
}

而在消費者方面:

while(!finished){
    while(!queue.isEmpty()){
        nextFile = queue.poll();
        if (nextFile.equals(NO_MORE_FILES_SIGNAL)) {
            finished = true;
            break;
        }
        doYourThingWith(nextFile);
    }
    if (!finished) {
        synchronized (outputQueue) {
            outputQueue.wait();
        }
    }
}

您可以在 Junit 測試用例中將值傳遞為零,而不是避免 Thread.sleep()。

暫無
暫無

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

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