如何在Android上測試異步任務
最近,在Sixt(德國比較大的一個汽車租賃網站)上,我們把我們的開發(fā)環(huán)境從Eclipse遷移到Android Studio。這也就意味著我們進入了新的編譯系統(tǒng)——Gradle,并且把TDD(測試驅動開發(fā))和CI(持續(xù)集成)納入我們的軟件開發(fā)流程。這里不是 討論在軟件開發(fā)中引入CI會帶來怎樣的好處,而是討論在Android中當測試UI之外的線程時會出現的問題。
Android中的測試(寬泛的定義)是一個單元測試集合的擴展。涉及初始化、關閉測試,包含setUp()和tearDown()操作,使用反射 的方式推斷出不同的測試方式(從JUnit4開始我們就可以使用注釋來指定的優(yōu)先級和執(zhí)行所有測試)。一個典型的測試結構如下:
- public class MyManagerTest extends ActivityTestCase {
- public MyManagerTest(String name) {
- super(name);
- }
- protected void setUp() throws Exception {
- super.setUp();
- }
- protected void tearDown() throws Exception {
- super.tearDown();
- }
- public void testDummyTest() {
- fail("Failing test");
- }
- }
這是一個非常明顯的示例:實際開發(fā)中,我們想要測試例如HTTP響應、SQL存儲等等。在Sixt我們遵從一種Manager/Model方法:每 個Model包含一個實體(車、顧客等)的表現。每個Manager用不同的模型(例如,我們的LoginManager可能需要用戶與之交互的模型)聚 合成一套功能。
大多數的Manager集中執(zhí)行HTTP請求是要從后臺獲取數據。例如,我們用下面的代碼來執(zhí)行用戶的登錄:
- mLoginManager.performLoginWithUsername("username", "password", new OnLoginListener() {
- @Override
- public void onFailure(Throwable throwable) {
- fail();
- }
- Override
- public void onSuccess(User customer) {
- //..
- }
- });
應用到我們自己的測試集合后,當得到預期之外的結果時,只是讓這一結果失敗。我們可以看到為什么在onFailure()函數中我們調用了 fail()。接下來,即使我用一個錯誤的用戶名也能通過這個測試。思前想后,測試似乎是按照代碼順序執(zhí)行的,但并沒有等到回調函數的結果返回再向下執(zhí) 行。
這顯然不是一個好方法。因為現在的程序經常通過異步任務和回調方法從后臺獲取數據。嘗試UIThread測試仍然不行。
***,我發(fā)現下面這種方法可以行得通。只是用簡單的CountDownLatch信號對象來實現wait-notify機制(你也可以用syncronized(lock){... lock.notify();},只是這樣代碼并不美觀而已)
那么之前的代碼就變成了下面的模樣:
- final CountDownLatch signal = new CountDownLatch(1);
- mLoginManager.performLoginWithUsername("username", "password", new OnLoginListener() {
- @Override
- public void onFailure(Throwable throwable) {
- fail();
- signal.countDown();
- }
- Override
- public void onSuccess(User customer) {
- signal.countDown();
- }
- });
- signal.await();