2014-02-21 28 views
4

我目前使用線程鎖定對我的異步方法進行單元測試,通常我會將CountDownLatch注入到異步組件中,並讓主線程等待它到達0.但是,這種方法看起來很醜陋,並且不會考慮一下當我爲一個組件編寫100多個測試時會發生什麼,他們都必須等待工作線程執行一些假異步作業。如何很好地測試異步方法?

那麼還有其他方法嗎?考慮一個簡單的搜索機制下面的例子:

Searcher.java

public class Searcher { 

    private SearcherListener listener; 

    public void search(String input) { 
     // Dispatch request to queue and notify listener when finished 
    } 

} 

SearcherListener.java

public interface SearcherListener { 

    public void searchFinished(String[] results); 

} 

你會如何單元測試search方法,無需使用多線程並阻止一個人等待另一個?我從How to use Junit to test asynchronous processes中得到了靈感,但是最好的答案並沒有提供具體的解決方案來解決這個問題。

+0

你想要測試什麼?搜索,還是你的排隊機制? –

+0

搜索,更具體地說,我需要我的搜索查詢來提供預期的結果。我想將假後端「注入」到搜索組件中,以便測試它如何響應API服務器的回調。 –

+0

如果你使用mockito,你可以創建一個'Answer',它在返回結果前休眠;不知道這是否對你有幫助? – fge

回答

1

針對異步編寫單元測試永遠看起來不錯。

需要testMyAsyncMethod()(主線程)阻塞,直到您準備好檢查正確的行爲。這是必要的,因爲測試用例在方法結束時終止。所以沒有辦法解決,問題只是你如何屏蔽。

不影響多大生產代碼的直接方法是 使用while循環:asume AsyncManager是被測類:

ArrayList resultTarget = new ArrayList(); 
AsyncManager fixture = new AsyncManager(resultTarget); 
fixture.startWork(); 
// now wait for result, and avoid endless waiting 
int numIter = 10; 
// correct testcase expects two events in resultTarget 
int expected = 2; 
while (numIter > 0 && resulTarget.size() < expected) { 
    Thread.sleep(100); 
    numIter--; 
} 
assertEquals(expected, resulTarget.size()); 

生產代碼將使用apropriate目標AsyncManager或構造使用另一個構造函數。爲了測試目的,我們可以通過我們的測試目標

您只會將其寫入固有的異步任務,如您自己的消息隊列。 其他代碼,只有統一執行計算任務的類的核心部分(特殊算法等),您不需要讓它在線程中運行。

但是,對於您的搜索監聽器而言,顯示的循環和等待原則是適當的。

public class SearchTest extends UnitTest implements SearchListener { 
    public void searchFinished() { 
    this.isSearchFinished = true; 
    } 

    public void testSearch1() { 
    // Todo setup your search listener, and register this class to receive 
    Searcher searcher = new Searcher(); 
    searcher.setListener(this); 
    // Todo setup thread 
    searcherThread.search(); 
    asserTrue(checkSearchResult("myExpectedResult1")); 

    } 

    private boolean checkSearchResult(String expected) { 
    boolean isOk = false; 
    int numIter = 10; 
    while (numIter > 0 && !this.isSearchFinished) { 
     Thread.sleep(100); 
     numIter--; 
    } 
    // todo somehow check that search was correct 
    isOk = ..... 

    return isOk; 
    } 
} 
+1

謝謝,但男人是這樣醜陋:) –

+0

感覺自由重構,使循環內部助手方法,你傳遞預期的結果,該助手返回true或false。 – AlexWien

+0

已更新爲具有輔助方法。 – AlexWien

1

另一種方法:

只是不啓動線程。就這樣。 Asume你有一個SearcherService它使用你的Searcher類。 然後不要啓動異步SearcherService,而只需調用searcher.search(),這會阻止搜索完成。

Searcher s = new Searcher(); 
s.search(); // blocks and returns when finished 
// now somehow check the result 
+0

我不確定我是否理解這個,你在封裝異步行爲並在同一個行爲中嘲笑它? –

+0

不,沒有模擬,生產實現有一個類SearcherService(或-Manager),你使用異步(註冊你的類來接收結果)。該服務在內部使用搜索引擎(類搜索器),該搜索引擎起作用synchrnouse。該服務提供了高效代碼中的異步訪問。對於單元測試,你只需測試內部搜索引擎,並假設異步Servcie工作(或通過我的其他答案測試) – AlexWien

1

創建類的同步版本,監聽其自己的結果,並使用內部鎖存器search()等待並searchFinished()清零。就像這樣:

public static class SynchronousSearcher implements SearcherListener { 
    private CountDownLatch latch = new CountDownLatch(1); 
    private String[] results; 

    private class WaitingSearcher extends Searcher { 
     @Override 
     public void search(String input) { 
      super.search(input); 
      try { 
       latch.await(); 
      } catch (InterruptedException e) { 
       throw new RuntimeException(e); 
      } 
     } 
    } 

    public String[] search(String input) { 
     WaitingSearcher searcher = new WaitingSearcher(); 
     searcher.listener = this; 
     searcher.search(input); 
     return results; 
    } 

    @Override 
    public void searchFinished(String[] results) { 
     this.results = results; 
     latch.countDown(); 
    } 
} 

然後使用它,簡單地說:

String[] results = new SynchronousSearcher().search("foo"); 

沒有主題,沒有等待循環,並在最少的時間可能在方法返回。如果搜索立即返回並不重要 - 在調用await()之前 - 因爲如果鎖存器已經爲零,則await()將立即返回。

+0

順便說一句,它花了我幾天的「後臺處理」,以瞭解如何可以做到這一點,因此延遲了應答 – Bohemian