2015-11-13 83 views
1

問題是我有一個方法開始一個耗時的工作的新線程。我想測試回調結果,但子線程可能仍在運行,因此,我得到的不是正確的存根。

我覺得代碼可以解釋本身:
如何單元測試java多線程

public class JustAClass { 
    //it is a callback for async 
    public interface JustACallBack { 
     void callFunc(JustAResult result); 
    } 

    //this is the result interface 
    public interface JustAResult { 
    } 

    //this is a real class for the interface 
    public class JustAResultReal implements JustAResult{ 
     public JustAResultReal(String content) {this.content = content;} 
     public String content; 
    } 

    //here is the key function 
    public void threadFunc(final JustACallBack callBack) { 
     BCCache.executorService.execute(new Runnable() { 
      @Override 
      public void run() { 
       //just to simulate a time-consuming task 
       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 

       //now we callback 
       callBack.callFunc(new JustAResultReal("can you reach me")); 
      } 
     }); 
    } 
} 

和測試功能可以(我使用mockito):

@Test 
public void testThreadFunc() throws Exception { 

    JustAClass justAClass = new JustAClass(); 

    JustAClass.JustACallBack callBack = Mockito.mock(JustAClass.JustACallBack.class); 

    justAClass.threadFunc(callBack); 

    //add this line, we can get the expected result 
    Thread.sleep(1200); 

    Mockito.verify(callBack).callFunc(captor.capture()); 

    System.out.println(((JustAClass.JustAResultReal)captor.getValue()).content); 
} 

我知道我們可以添加一個sleep等待,期望子線程會在這段時間內退出,但是有沒有更好的方法?其實我怎麼知道子線程需要多長時間?設置很長時間可以是一種方法,但看起來不是很好。

+0

IMO:我會從單元測試中刪除自己的線程方面,並測試線程內部的邏輯。我相信Java中的線程工作原理,我不需要測試它。並且測試邏輯的線程對於你並行運行的代碼的工作方式是集成測試的一部分,而不是單元測試。 – Gimby

+0

問題是,您正在使用靜態ExecutorService。爲了使事情更加單元化,您可以傳入_mock_ ExecutorService,然後驗證被測模塊是否以預期方式與模擬進行了交互。 –

+0

如果線程是您正在測試的代碼的*點,那麼在單元測試中進行線程沒有什麼問題。請參閱我的答案,瞭解如何正確執行此操作。 – Jonathan

回答

0

當你開始測試線程方面的時候,我對@Gimbys的評論意見不再是單元測試。

儘管如此,作爲集成測試異步調用的一種方法很有趣。

爲了避免睡眠,我傾向於使用類CountDownLatch來等待invokations。 爲了倒計時,你需要一個真正的回調接口實現 - 所以在我的例子中,我已經做了這個模擬實現。

由於沒有實際的方法來獲取數據 - 我只是測試它實際上是JustAReal接口的一個實例。

@Test 
public void testInvoke() throws Exception { 

    final CountDownLatch countDownLatch = new CountDownLatch(1); //1 is how many invokes we are waiting for 

    JustAClass justAClass = new JustAClass(); 
    JustAClass.JustACallBack callBack = new JustAClass.JustACallBack() { 
     @Override 
     public void callFunc(final JustAClass.JustAResult result) { 
      assertNotNull("Result should not be null", result); 
      assertTrue("Result should be instance of JustAResultReal", result instanceof JustAClass.JustAResultReal); 
      countDownLatch.countDown(); 
     } 
    }; 

    justAClass.threadFunc(callBack); 
    if(!countDownLatch.await(1200, TimeUnit.MILLISECONDS)){ 
     fail("Timed out, see log for errors"); 
    } 

} 
+0

這種方法實際上不起作用,因爲任何斷言失敗都不會被主測試線程注意到。 – Jonathan

+0

你是對的,斷言失敗可能會在日誌中 - 我們需要檢查鎖存器是否正確計數,以使測試失敗,如:if(!countDownLatch.await(1200,TimeUnit.MILLISECONDS)){ 失敗(「Timed out」); '我會更新答案。 – stalet

1

在@ stalet的回答一般的方法是關閉的,但也並非完全奏效,因爲從一個單獨的線程的任何斷言失敗不是由主線程注意到。所以你的測試將會總是通過,即使它不應該。相反,嘗試使用ConcurrentUnit

@Test 
public void testInvoke() throws Throwable { 
    Waiter waiter = new Waiter(); 
    JustAClass justAClass = new JustAClass(); 
    JustAClass.JustACallBack callBack = new JustAClass.JustACallBack() { 
     @Override 
     public void callFunc(final JustAClass.JustAResult result) { 
      waiter.assertNotNull(result); 
      waiter.assertTrue(result instanceof JustAClass.JustAResultReal); 
      waiter.resume(); 
     } 
    }; 

    justAClass.threadFunc(callBack); 
    waiter.await(1200, TimeUnit.SECONDS); 
} 

的這裏關鍵是ConcurrentUnit的Waiter將正確報告任何斷言失敗的主要測試線和測試將通過或失敗,因爲它應該。

+0

這個API會立即中斷主線程嗎?如果你有'waiter.await(10,TimeUnit.MINUTES)',如果測試已經失敗了,你不必等待超過必要的時間? – stalet

+0

@stalet是的,失敗的斷言或'waiter.resume()'調用會立即中斷正在等待的主線程。如果發生故障,它將自動重新拋出,從而使測試失敗。 – Jonathan

+1

是的。那麼這顯然更好。 +1 :) – stalet