bounty将在6天内到期。回答此问题可获得+150声望奖励。user1506104希望引起更多关注这个问题。
我创建了这个短类,它在执行某个进程后向侦听器提供响应。为了使其成为reproducible example,我修改了我的 handleMessage(Message) 函数,使其休眠5秒,并在完成时返回一个随机字符串,而不是实际连接到远程主机。
public class MyService {
private static final String TAG = MyService.class.getSimpleName();
private final HandlerThread mInboxHandlerThread;
private final Handler mInboxHandler;
private final List<Handler> mListeners = new ArrayList<>();
public MyService() {
mInboxHandlerThread = new HandlerThread("Incoming data handler");
mInboxHandlerThread.start();
mInboxHandler = new Handler(mInboxHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
MyService.this.handleMessage(msg);
}
};
}
public final void inform(Message msg) {
if (mInboxHandler != null) {
mInboxHandler.sendMessage(msg);
} else {
Log.w(TAG, "mInboxHandler is null");
}
}
public final void registerListener(Handler listener) {
mListeners.add(listener);
}
public final void unregisterListener(Handler listener) {
mListeners.remove(listener);
}
final void notifyOutboxHandlers(int what, int arg1, int arg2, Object obj) {
if (mListeners.isEmpty()) {
Log.w(TAG, String.format("No listener to handle outgoing message (%d)", what));
} else {
for (Handler handler : mListeners) {
Message msg = Message.obtain(handler, what, arg1, arg2, obj);
msg.sendToTarget();
}
}
}
private void handleMessage(Message msg) {
Thread thread = new Thread() {
@Override
public void run() {
try {
// Mimic long-running service execution
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyOutboxHandlers(COMMAND_RESPONSE, 0, 0, "<SUCCESS/>");
}
};
thread.start();
}
}
这门课起作用了。我只需要编写一个单元测试,并确保可以从服务触发侦听器(类型为Handler)。我的视图(类型为Activity)层就像下面这样简单:
public class MainActivity extends AppCompatActivity implements Handler.Callback {
private final static String TAG = MainActivity.class.getSimpleName();
private MyService mMyService = null;
private TextView mMessagesView = null;
private Handler mHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cc);
mMessagesView = findViewById(R.id.messages);
mMyService = new MyService();
mHandler = new Handler(this);
mMyService.registerListener(mHandler);
Message test = new Message();
test.what = COMMAND_REQUEST;
test.arg1 = 1;
test.arg2 = 2;
test.obj = "test";
mMyService.inform(test);
}
@Override
public boolean handleMessage(@NonNull Message msg) {
String messages = mMessagesView.getText().toString();
mMessagesView.setText(messages + "\nTransactionManager: " + msg.obj);
switch (msg.what) {
case COMMAND_RESPONSE:
Log.d(TAG, "got response from controller");
return true;
}
return false;
}
@Override
protected void onDestroy() {
mMyService.unregisterListener(mHandler);
super.onDestroy();
}
}
最后,我的问题是,如何对这个回调函数进行单元测试?如何确保视图层中的处理程序被正确触发。具体来说,我想测试/Assert来自服务的回复是<SUCCESS/>
。
通过参考其他堆栈溢出帖子,我能够创建此测试脚本,但甚至无法工作:
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
private MyService mMyService;
private Handler mHandler;
@Mock
private Handler.Callback mHandlerCallback;
@Captor
private ArgumentCaptor<Handler.Callback> callbackCaptor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mMyService = new MyService();
when(mHandlerCallback.handleMessage(any(Message.class)))
.thenReturn(true);
}
@Test
public void testCallback() {
String message = "test";
Message test= new Message();
test.what = COMMAND_REQUEST;
test.arg1 = 1;
test.arg2 = 2;
test.obj = message;
mHandler = new Handler(callbackCaptor.capture());
mMyService.registerListener(mHandler);
mMyService.inform(test);
assertTrue(callbackCaptor instanceof Handler.Callback );
}
}
任何帮助将不胜感激。谢谢!
1条答案
按热度按时间2nc8po8w1#
测试异步代码可能很棘手,因为测试运行器并不等待异步任务完成。在本例中,您尝试测试由异步操作调用的回调。
一种方法是使用
CountDownLatch
,这是Java中的并发工具,允许一个线程等待,直到另一个线程满足特定条件。(另见Eugen Baeldung的“Guide to CountDownLatch in Java”)例如,您可以使用以下命令修改测试类:
CountDownLatch
用于暂停测试方法的执行,直到调用回调。锁存器在处理程序的handleMessage
方法中倒计时,该方法是在消息发送到处理程序时调用的方法。如果在10秒内调用回调,则测试通过。ArgumentCaptor
用于捕获发送到回调的Message
对象,以便您可以Assert它是您所期望的。请确保根据您的服务中操作所需的时间相应地调整
latch.await(10, TimeUnit.SECONDS)
中的时间。如果操作花费的时间超过提供的超时时间,则测试将失败。与原始代码的区别:
1.**CountDownLatch:**在更新后的代码中,创建了一个计数为1的
CountDownLatch
。这个锁存器的作用是暂停测试方法的执行,直到调用回调。当调用回调函数时,锁存器将倒计时。如果锁存器在指定的超时(本例中为10秒)内倒计时,则测试通过。如果在超时时间内没有调用回调,则测试失败。这是用于测试异步代码的常用技术。1.**创建句柄:**原代码中,
Handler
是用ArgumentCaptor
捕获的Callback
创建的。这样做是为了捕获Callback
对象,以便稍后进行验证。然而,在更新的代码中,Handler
是用Callback
创建的,当CountDownLatch
收到消息时,它会倒计时。这样做是为了发出已调用回调的信号。1.**Assert:**在原始代码中,测试Assert
Callback
是Handler.Callback
的示例。但是,在更新的代码中,测试Assert回调接收到的Message
是预期的。这是一个更有意义的测试,因为它验证服务是否按预期运行,而不仅仅是它是否调用回调。1.**消息捕获:**在更新后的代码中,使用
ArgumentCaptor
捕获发送给回调的Message
对象。这样做是为了让您可以Assert消息是您所期望的。在原始代码中,没有这样的捕获。1.**异常处理:**更新后的测试方法声明抛出
InterruptedException
。这是必需的,因为如果当前线程在等待闩锁倒计时时中断,latch.await()
可能会抛出此异常。