Robolectric Test Handler

在 Android 的開發中,難免避免不掉使用多線程,其中 Handler 是多線程中,不可或缺的角色。

但這邊不是示範如何使用 Handler,而是示範透過 Robolectric 來測試 Handler。

Environment:

  • Robolectric Ver. 2.4
  • Mac OS X 10.10
  • Android Studio 1.0.2

首先,先準備好 SetUp 和 TearDown 方法。

接著,準備兩個受測程式:

  • runnable 每執行一次就 counter 加1
  • handler 每 handlerMessage 一次就 counter 加10
HandlerTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import android.os.Handler;
import android.os.Message;

import junit.framework.TestCase;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(emulateSdk = 18)
public class HandlerTest extends TestCase{

int counter;
Runnable runnable;
Handler handler;

@Before
public void setUp() throws Exception {
super.setUp();

counter = 0;

runnable = new Runnable() {
@Override
public void run() {
counter++;
}
};

handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
counter += 10;
}
};
}

@After
public void tearDown() throws Exception {
runnable = null;
handler = null;
super.tearDown();
}
}

然後我們逐一加上 TestCase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
public void testM1() throws Exception{
assertEquals(0,counter);

handler.sendEmptyMessage(0);

assertEquals(10, counter);

handler.sendEmptyMessageDelayed(0, 0);
assertEquals(20,counter);

handler.sendEmptyMessageDelayed(0,1);
handler.sendEmptyMessageDelayed(0,1000);
handler.sendEmptyMessageDelayed(0,10000);

assertEquals(20, counter);
}

@Test
public void testR1() throws Exception{
assertEquals(0,counter);

handler.post(runnable);

assertEquals(1, counter);

handler.postDelayed(runnable,0);
assertEquals(2,counter);

handler.postDelayed(runnable,1);
handler.postDelayed(runnable,1000);
handler.postDelayed(runnable,10000);

assertEquals(2, counter);
}

從這個測試來看,我們可以明顯看到最後三條 handler.sendEmptyMessageDelayed 並沒有執行的效果。

這個時候我們可能會覺得說,應該是執行的時間不夠,所以這個測試案例並不能正常跑完。

所以,在這最後面加入 Thread.sleep(10000),期望他能夠順利跑完

PS : Don’t do that, it only let your test wait 10 sec, but handlerMessage still not working


handler.sendEmptyMessage(0)handler.sendEmptyMessageDelayed(0, 0) 屬於立即執行,所以馬上可以得到結果

不該使用 Thread.sleep(10000) , Robolectric 有提供一些好用的方法(idle)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Test
public void testM2() throws Exception{
handler.sendEmptyMessageDelayed(0,1);
handler.sendEmptyMessageDelayed(0,1000);
handler.sendEmptyMessageDelayed(0,10000);
Robolectric.idleMainLooper(10000);
assertEquals(30, counter);

handler.sendEmptyMessageDelayed(0,1);
handler.sendEmptyMessageDelayed(0,1000);
handler.sendEmptyMessageDelayed(0,10000);
ShadowLooper.idleMainLooper(10000);
assertEquals(60, counter);

ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());
handler.sendEmptyMessageDelayed(0,1);
handler.sendEmptyMessageDelayed(0,1000);
handler.sendEmptyMessageDelayed(0,10000);
shadowLooper.idle(10000);
assertEquals(90, counter);
}

@Test
public void testR2() throws Exception{
handler.postDelayed(runnable,1);
handler.postDelayed(runnable,1000);
handler.postDelayed(runnable,10000);
Robolectric.idleMainLooper(10000);
assertEquals(3, counter);

handler.postDelayed(runnable,1);
handler.postDelayed(runnable,1000);
handler.postDelayed(runnable,10000);
ShadowLooper.idleMainLooper(10000);
assertEquals(6, counter);

ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());
handler.postDelayed(runnable,1);
handler.postDelayed(runnable,1000);
handler.postDelayed(runnable,10000);
shadowLooper.idle(10000);
assertEquals(9, counter);
}

以及我們可透過操控 Looper 的方式,去掌控 handler。

用 shadowLooper.runOneTask 一次執行一個 Task。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
public void testM3() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.sendEmptyMessageDelayed(0,1);
shadowLooper.runOneTask();
assertEquals(10, counter);

handler.sendEmptyMessageDelayed(0,1000);
shadowLooper.runOneTask();
assertEquals(20, counter);

handler.sendEmptyMessageDelayed(0,10000);
shadowLooper.runOneTask();
assertEquals(30, counter);
}

@Test
public void testR3() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.postDelayed(runnable,1);
shadowLooper.runOneTask();
assertEquals(1, counter);

handler.postDelayed(runnable,1000);
shadowLooper.runOneTask();
assertEquals(2, counter);

handler.postDelayed(runnable,10000);
shadowLooper.runOneTask();
assertEquals(3, counter);
}

或者用 shadowLooper.runToEndOfTasks 一次把所有的 Tasks 執行完。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testM4() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.postDelayed(runnable,1);
handler.postDelayed(runnable,1000);
handler.postDelayed(runnable,10000);
shadowLooper.runToEndOfTasks();
assertEquals(3, counter);
}

@Test
public void testR4() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.sendEmptyMessageDelayed(0,1);
handler.sendEmptyMessageDelayed(0,1000);
handler.sendEmptyMessageDelayed(0,10000);
shadowLooper.runToEndOfTasks();
assertEquals(30, counter);
}

除了 runOneTask 之外,還有一個相似的方法 runToNextTask

上半段送出了三個同時間的 Task,並要求執行一個任務,因此結果為”10”

下半段也送出了三個同時間的 Task,並要求把最近一次的 tasks(b1, b2, b3)做執行,因此結果為”10 + 30”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test5() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.sendEmptyMessageDelayed(0,1); // task a1
handler.sendEmptyMessageDelayed(0,1); // task a2
handler.sendEmptyMessageDelayed(0,1); // task a3
shadowLooper.runOneTask(); // run task a1
handler.removeCallbacksAndMessages(null);
shadowLooper.runToEndOfTasks(); // remove a2 a3
assertEquals(10,counter);

handler.sendEmptyMessageDelayed(0,1); // task b1
handler.sendEmptyMessageDelayed(0,1); // task b2
handler.sendEmptyMessageDelayed(0,1); // task b3
shadowLooper.runToNextTask(); // run b1 b2 b3
handler.removeCallbacksAndMessages(null);
shadowLooper.runToEndOfTasks();
assertEquals(40,counter);
}


下面的案例,邊寫才發現一些蹊蹺,稍微翻了下Source,shadowLooper會把丟進來的task交給scheduler處理,
依照current time and delay time 進行sort,並放在MessageQueue中,因為這樣的設計,我們丟進去的任務才不會亂掉

當我們執行runOneTask 或者 runToNextTask,就會從MessageQueue中拿一個任務出來,並做了時間推移。

接下來用test6來解釋!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test6() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.sendEmptyMessageDelayed(0,1); // register 1
shadowLooper.runOneTask(); // now 1
assertEquals(1,shadowLooper.getScheduler().getCurrentTime());

handler.sendEmptyMessageDelayed(0,1); // register 2
shadowLooper.runOneTask(); // now 2
assertEquals(2,shadowLooper.getScheduler().getCurrentTime());

handler.sendEmptyMessageDelayed(0,1); // register 3
shadowLooper.runOneTask(); // now 3
assertEquals(3,shadowLooper.getScheduler().getCurrentTime());

handler.sendEmptyMessageDelayed(0,1); // register 4 a
handler.sendEmptyMessageDelayed(0,1); // register 4 b
handler.sendEmptyMessageDelayed(0,1); // register 4 c
shadowLooper.runOneTask(); // now 4 run a
shadowLooper.runOneTask(); // now 4 run b
shadowLooper.runOneTask(); // now 4 run c
assertEquals(4,shadowLooper.getScheduler().getCurrentTime());
}


另外一個坑是removeCallbacksAndMessages(null)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Test
public void test7() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());

handler.sendEmptyMessageDelayed(0,1); // register 1
handler.sendEmptyMessageDelayed(0,1000); // register 1000
handler.sendEmptyMessageDelayed(0,10000); // register 10000
handler.sendEmptyMessageDelayed(0,20000); // register 20000
shadowLooper.runOneTask(); // now 1
handler.removeCallbacksAndMessages(null);
assertEquals(10, counter);

handler.sendEmptyMessageDelayed(0,1); // register 2
handler.sendEmptyMessageDelayed(0,1000); // register 1001
handler.sendEmptyMessageDelayed(0,10000); // register 10001
handler.sendEmptyMessageDelayed(0,20000); // register 20001

shadowLooper.runOneTask();
assertEquals(20, counter);
assertEquals(2,shadowLooper.getScheduler().getCurrentTime());

shadowLooper.runOneTask();
assertEquals(20, counter);
assertEquals(1000,shadowLooper.getScheduler().getCurrentTime());// remove 1000

shadowLooper.runOneTask();
assertEquals(30, counter);
assertEquals(1001,shadowLooper.getScheduler().getCurrentTime());

shadowLooper.runOneTask();
assertEquals(30, counter);
assertEquals(10000,shadowLooper.getScheduler().getCurrentTime());// remove 10000

shadowLooper.runOneTask();
assertEquals(40, counter);
assertEquals(10001,shadowLooper.getScheduler().getCurrentTime());

shadowLooper.runOneTask();
assertEquals(40, counter);
assertEquals(20000,shadowLooper.getScheduler().getCurrentTime());// remove 20000

shadowLooper.runOneTask();
assertEquals(50, counter);
assertEquals(20001,shadowLooper.getScheduler().getCurrentTime());
}

接著是idleConstantly,預設為false,打開之後相當於自排效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test8() throws Exception{
ShadowLooper shadowLooper = Robolectric.shadowOf(handler.getLooper());
shadowLooper.idleConstantly(true);

handler.sendEmptyMessageDelayed(0,1);
assertEquals(10, counter);
handler.sendEmptyMessageDelayed(0,1000);
assertEquals(20, counter);
handler.sendEmptyMessageDelayed(0,10000);
assertEquals(30, counter);
handler.sendEmptyMessageDelayed(0,20000);
assertEquals(40, counter);

assertEquals(0,shadowLooper.getScheduler().getCurrentTime());
}

Source Code At Gists

End