2010-09-07 67 views
13

我使用android.os.Handler類在後臺執行任務。當單元測試這些時,我打電話Looper.loop()使測試線程等待後臺任務線程完成它的工作。之後,我打電話給Looper.myLooper().quit()(也在測試線程中),以允許測試線程退出loop並恢復測試邏輯。如何更好地在Android上測試Looper和Handler代碼?

這是所有罰款和丹迪直到我想寫多個測試方法。

的問題是,尺蠖似乎並沒有被設計成允許退出並重新啓動在同一個線程,所以我被迫做了我所有的測試單個測試方法中。

我查看了Looper的源代碼,找不到解決方法。

是否有任何其他方式來測試我的手杖/活套代碼?或者,也許更多的測試友好的方式來寫我的後臺任務類?

+0

你能爲此發佈一些示例代碼嗎?我基本上有同樣的問題,除非我沒有達到你的程度。 – 2011-02-06 16:11:20

回答

0

我已經在你的同一個問題偶然發現。我也想爲一個使用Handler的課程做一個測試用例。

同你做了什麼,我用的是Looper.loop()有測試線程開始處理在處理程序的排隊的消息。

要停止它,我用的MessageQueue.IdleHandler實施當彎阻塞,等待下一步消息來通知我。當它發生時,我打電話給quit()方法。但是,當我做出多個測試用例時,同樣如此,我遇到了問題。

我不知道你已經解決了這個問題,也許照顧我(可能還有其他人)分享:)

PS:我也想知道你怎麼稱呼你的Looper.myLooper().quit()

謝謝!

3

對活套的源代碼顯示,Looper.myLooper()。退出()在消息隊列中,它告訴活套,它是永遠的完成處理消息排入空消息。從本質上講,線程在那一刻變成了一個死線程,並且沒有辦法讓我知道的恢復它。在quit()被調用後,嘗試將消息發送到 Handler時,您可能會看到錯誤消息,說明嘗試將消息發送到無效線程。那就是這個意思。

2

這實際上可以如果不使用AsyncTask通過引入第二彎針線(比由Android爲您創建隱式主一個其他)容易測試。然後,基本策略是使用CountDownLatch阻止主循環者線程,同時將所有回調委託給第二循環線程。

的這裏需要注意的是在測試你的代碼必須能夠使用默認主以外的活套支持。我認爲,無論支持更強大和更靈活的設計,情況都應該如此,並且幸運的是非常簡單。一般而言,所有必須完成的工作是修改代碼以接受可選的Looper參數,並使用該參數構造您的Handler(如new Handler(myLooper))。對於AsyncTask,此要求使得無法使用此方法對其進行測試。我認爲應該用AsyncTask本身來解決這個問題。

一些示例代碼,您開始:

public void testThreadedDesign() { 
    final CountDownLatch latch = new CountDownLatch(1); 

    /* Just some class to store your result. */ 
    final TestResult result = new TestResult(); 

    HandlerThread testThread = new HandlerThread("testThreadedDesign thread"); 
    testThread.start(); 

    /* This begins a background task, say, doing some intensive I/O. 
    * The listener methods are called back when the job completes or 
    * fails. */ 
    new ThingThatOperatesInTheBackground().doYourWorst(testThread.getLooper(), 
      new SomeListenerThatTotallyShouldExist() { 
     public void onComplete() { 
      result.success = true; 
      finished(); 
     } 

     public void onFizzBarError() { 
      result.success = false; 
      finished(); 
     } 

     private void finished() { 
      latch.countDown(); 
     } 
    }); 

    latch.await(); 

    testThread.getLooper().quit(); 

    assertTrue(result.success); 
} 
0

通過@Josh吉爾福伊爾的回答啓發,我決定嘗試使用反射來得到我需要什麼,以使自己的無阻塞訪問和非戒菸Looper.loop()

/** 
* Using reflection, steal non-visible "message.next" 
* @param message 
* @return 
* @throws Exception 
*/ 
private Message _next(Message message) throws Exception { 
    Field f = Message.class.getDeclaredField("next"); 
    f.setAccessible(true); 
    return (Message)f.get(message); 
} 

/** 
* Get and remove next message in local thread-pool. Thread must be associated with a Looper. 
* @return next Message, or 'null' if no messages available in queue. 
* @throws Exception 
*/ 
private Message _pullNextMessage() throws Exception { 
    final Field _messages = MessageQueue.class.getDeclaredField("mMessages"); 
    final Method _next = MessageQueue.class.getDeclaredMethod("next"); 

    _messages.setAccessible(true); 
    _next.setAccessible(true); 

    final Message root = (Message)_messages.get(Looper.myQueue()); 
    final boolean wouldBlock = (_next(root) == null); 
    if(wouldBlock) 
     return null; 
    else 
     return (Message)_next.invoke(Looper.myQueue()); 
} 

/** 
* Process all pending Messages (Handler.post (...)). 
* 
* A very simplified version of Looper.loop() except it won't 
* block (returns if no messages available). 
* @throws Exception 
*/ 
private void _doMessageQueue() throws Exception { 
    Message msg; 
    while((msg = _pullNextMessage()) != null) { 
     msg.getTarget().dispatchMessage(msg); 
    } 
} 

現在,在我的測試中(這需要在UI線程上運行),我現在可以這樣做:

@UiThreadTest 
public void testCallbacks() throws Throwable { 
    adapter = new UpnpDeviceArrayAdapter(getInstrumentation().getContext(), upnpService); 

    assertEquals(0, adapter.getCount()); 

    upnpService.getRegistry().addDevice(createRemoteDevice()); 
    // the adapter posts a Runnable which adds the new device. 
    // it has to because it must be run on the UI thread. So we 
    // so we need to process this (and all other) handlers before 
    // checking up on the adapter again. 
    _doMessageQueue(); 

    assertEquals(2, adapter.getCount()); 

    // remove device, _doMessageQueue() 
} 

我並不是說這是一個好主意,但到目前爲止,它已經爲我工作。值得嘗試!我喜歡的是Exceptions被扔進一些hander.post(...)將打破測試,否則情況就不一樣了。