我正在为 Android 应用程序编写一个简单的类来充当计时器,它将定期更新 Activity UI。计时器类使用 Handler postDelayed() 函数来执行 Runnable。
这是计时器的代码:
public class MyTimer
{
private MyTimerDelegate _delegate;
private int _seconds = 0;
public MyTimerDelegate delegate() {
return _delegate;
}
public void setDelegate(MyTimerDelegate delegate) {
_delegate = delegate;
}
public int seconds() {
return _seconds;
}
public void setSeconds(int seconds) {
if (seconds < 0)
throw new IllegalArgumentException();
_seconds = seconds;
}
public void start() throws Exception {
if (_delegate == null)
throw new Exception("A delegate has not been set.");
_delegate.MyTimerStartedFromSeconds(this, _seconds);
// Schedule the first timer fire.
final Handler handler = new Handler();
final MyTimer MyTimer = this;
handler.postDelayed(new Runnable() {
@Override
public void run() {
// The timer fired. Decrement seconds.
_seconds -= 1;
// Inform the delegate.
_delegate.MyTimer_countedDownToSeconds(MyTimer, _seconds);
// If there are seconds remaining, schedule the next fire.
// Otherwise, stop the timer.
if (_seconds > 0) {
handler.postDelayed(this, 1000);
} else {
_delegate.MyTimerStopped(MyTimer);
}
}
}, 1000);
}
}
我的问题是:如何编写 JUnit 4 测试以确保 MyTimer 在倒计时期间的每个时间间隔都调用其委托?
这是我到目前为止所做的:
public void testCountdownCallsDelegate() throws Exception {
class MockMyTimerDelegate implements MyTimerDelegate {
public int callbackCount = 0;
@Override
public void biteTimerStartedFromSeconds(BiteTimer biteTimer, int seconds) {
}
@Override
public void biteTimerStopped(BiteTimer biteTimer) {
}
@Override
public void biteTimer_countedDownToSeconds(BiteTimer biteTimer, int seconds) {
callbackCount++;
}
}
MockMyTimerDelegate delegate = new MockMyTimerDelegate();
MyTimer timer = new MyTimer();
timer.setDelegate(delegate);
timer.setSeconds(1);
timer.start();
// Need some way to wait here and allow the timer to call its delegate...
assertEquals("The timer should have called the delegate when counting down.", 1, delegate.callbackCount);
}
显然,我不能用 Thread.sleep() 调用阻塞线程。有任何想法吗?
找到解决方案
这个解决方案似乎有效。我把它贴在这里以防它帮助别人。如果你有办法,请改进它。
public void testMyTimerDelegateIsCalledForEachSecondDuringCountdown() throws Exception {
// An object to sync our threads with.
Object syncLock = new Object();
// This thread class will perform the actual test.
class TimerThread extends Thread
{
Object _syncLock;
TimerThread(Object syncLock) {
_syncLock = syncLock;
}
@Override
public void run() {
// Since MyTimer is calling Handler postDelayed(), we need a Looper.
Looper.prepare();
// myTimer was instantiated in setUp()
myTimer.setDelegate(new MockMyTimerDelegate() {
@Override
public void myTimer_countedDownToSeconds(MyTimer myTimer, int seconds) {
countdownToSecondsCallCount++;
if (countdownToSecondsCallCount >= 3) {
synchronized (_syncLock) {
// The test is complete. Now the parent thread can continue and assert the results.
_syncLock.notify();
}
}
}
});
myTimer.setSeconds(3);
try {
myTimer.start();
Looper.loop();
} catch (Exception e) {
}
}
}
// Run the test on its own thread and wait for it to finish.
TimerThread t = new TimerThread(syncLock);
t.start();
synchronized (syncLock) {
// Wait more time than should be needed, but timeout in case of failure.
syncLock.wait(5000);
}
assertEquals("MyTimerDelegate should have been called 3 times during three second countdown.", 3, ((MockMyTimerDelegate) myTimer.delegate()).countdownToSecondsCallCount);
}