Intereting Posts
Конструктор ArrayAdapter <string> не определен Фрагмент воссоздается каждый раз после изменения ориентации, не может восстановить состояние Как передать параметры командной строки эмулятору в Android Studio? Каждая сборка NDK – это полная перестройка Как я могу удалить ненужное верхнее дополнение вида навигации? Не удалось создать мост отладки Не удалось обнаружить версию adb. Синтаксическая ошибка: ")" Обновление платформ-инструментов с 23.0.1 по 23.1.0 Линукс-32 бит вызывает проблему Завершение действия из другого класса Как читать выбранное приложение для определения местоположения в Android M – API 23 Каков путь по умолчанию для debug.keystore на Mac? Android TabLayout больше не отображает содержимое, как только фрагмент переключается Синхронизация проекта Android Gradle завершилась неудачно из-за целевой версии Полноэкранный диалог с использованием прозрачного StatusBar Android Studio – с Junit 4.12 "!!! Ожидается, что версия JUnit версии 3.8 или более поздней версии: Ошибка Parse: Parse # enableLocalDatastore (Context) `необходимо вызвать перед` Parse # initialize (Context) ` Что лучше с точки зрения производительности синтаксического анализа в Android, XML или JSON?

Как лучше тестировать код Looper и Handler на Android?

Я использую класс android.os.Handler для выполнения задач на фоне. Когда модуль тестирует их, я вызываю Looper.loop() чтобы тестовый поток Looper.loop() когда поток фоновой задачи выполнит свою задачу. Позже я вызываю Looper.myLooper().quit() (также в тестовом потоке), чтобы позволить тестовому потоку выйти из loop и возобновить логику тестирования.

Все отлично и денди, пока я не хочу писать более одного метода тестирования.

Проблема в том, что Looper, похоже, не предназначен для того, чтобы разрешить выход и перезапуск в одном потоке, поэтому я вынужден выполнять все мои тесты внутри одного тестового метода.

Я заглянул в исходный код Лоупера и не смог найти пути вокруг него.

Есть ли другой способ проверить мой код Hander / Looper? Или, может быть, еще один удобный способ написать мой фоновый класс задач?

Solutions Collecting From Web of "Как лучше тестировать код Looper и Handler на Android?"

Исходный код Looper показывает, что Looper.myLooper (). Quit () помещает нулевое сообщение в очередь сообщений, которое сообщает Looper, что он обрабатывает сообщения FOREVER. По существу, поток становится мертвой нитью в этой точке, и нет никакого способа оживить его, о котором я знаю. Возможно, вы видели сообщения об ошибках при попытке отправить сообщения обработчику после того, как quit () вызывается эффекту «попытка отправить сообщение в мертвый поток». Вот что это значит.

Это может быть легко протестировано, если вы не используете AsyncTask , введя второй поток петлителя (кроме основного, созданного для вас неявно Android). Основная стратегия заключается в том, чтобы заблокировать поток основного петлителя с помощью CountDownLatch , делегируя все ваши обратные вызовы во второй поток петлителя.

Здесь предостережение заключается в том, что ваш тестируемый код должен поддерживать использование петлителя, отличного от основного по умолчанию. Я бы сказал, что это должно быть так, независимо от поддержки более надежного и гибкого дизайна, и это также очень легко. В общем, все, что нужно сделать, – это изменить свой код, чтобы принять необязательный параметр Looper и использовать его для создания Handler (как new Handler(myLooper) ). Для AsyncTask это требование не позволяет проверить его с помощью этого подхода. Проблема, которая, как мне кажется, должна быть исправлена ​​с 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); } 

Я наткнулся на ту же проблему, что и ваша. Я также хотел создать тестовый пример для класса, использующего Handler .

То же, что и вы, я использую Looper.loop() чтобы тестовый поток начал обрабатывать очереди в обработчике.

Чтобы остановить его, я использовал реализацию MessageQueue.IdleHandler чтобы уведомить меня, когда петлитель блокирует ожидание следующего следующего сообщения. Когда это произойдет, я вызываю метод quit() . Но опять же, как и у меня, у меня возникла проблема, когда я делаю более одного теста.

Интересно, вы уже решили эту проблему и, возможно, поделитесь ею со мной (и, возможно, с другими) 🙂

PS: Мне также хотелось бы узнать, как вы называете Looper.myLooper().quit() .

Благодаря!

Вдохновленный ответом @Josh Guilfoyle, я решил попытаться использовать рефлексию, чтобы получить доступ к тому, что мне нужно, чтобы сделать свой собственный неблокирующий и не 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); } } 

Теперь в моих тестах (которые нужно запустить в потоке пользовательского интерфейса) я могу теперь сделать:

 @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(...) , нарушат тесты, а это не так.