Как протестировать пользовательский интерфейс Android с помощью IdlingResource при использовании сетевых запросов Retrofit

Я пишу интеграционные тесты, которые выполняют действия в пользовательском интерфейсе, запускающие сетевые вызовы с помощью Retrofit.

Я знаю, что мне нужно реализовать CountingIdlingResource, но я хочу сделать это правильно (а не изобретать велосипед, если это уже было сделано) .

Кто-нибудь реализовал IdlingResource в наборе тестов Espresso своего приложения для ожидания выполнения сетевых запросов?

Подробнее здесь.


person Austyn Mahoney    schedule 17.04.2014    source источник
comment
Я никогда не использовал Retrofit, но, просматривая их API, когда вы выполняете Client.execute внутри AsyncTask, вам не нужен IdlingResource, потому что пул потоков AsyncTask контролируется Espresso. Если по какой-то причине вы не можете использовать AsyncTask, вы все равно можете получить выгоду от мониторинга этого пула потоков, запустив задачу через AsyncTask.THREAD_POOL_EXECUTOR.submit(). Существует так много (разрешимых) ловушек с использованием IdlingResources, что, исходя из моего опыта, лучше иметь как можно меньше.   -  person haffax    schedule 18.04.2014
comment
Модернизация использует собственный пул потоков/исполнитель, он не использует AsyncTasks. IdlingResource должен быть реализован в нем, я просто хотел знать, пробовал ли кто-то еще это.   -  person Austyn Mahoney    schedule 18.04.2014
comment
Я использовал пользовательские IdlingResources и CountingIdlingResource для различных задач, включая сетевой запрос. Не зная больше о том, как действие пользовательского интерфейса инициирует запрос и как обрабатывается ответ/отмена, трудно дать конкретный совет. Либо используйте декоративный подход из связанного примера, либо реализуйте простой API прослушивателя, к которому тестовый класс может подключить IdlingResource, чтобы узнать, когда ожидается завершение сетевого запроса и когда он завершен или прерван.   -  person haffax    schedule 18.04.2014
comment
Я ищу кого-то, кто специально реализовал это с помощью Retrofit и его метода RestAdapter.Builder().setExecutor(...). Ничего страшного, если я не получу ответа, но я немного подожду, прежде чем писать свою собственную реализацию.   -  person Austyn Mahoney    schedule 18.04.2014
comment
@AustynMahoney Я ищу то же самое (рекомендуемый подход), какие-либо обновления в вашем поиске?   -  person Kaushik Gopal    schedule 03.05.2014


Ответы (5)


Самое простое решение для этого: заменить исполнитель пула потоков Retrofit на AsyncTask (как рекомендовал очень полезный Ник из этого связанное обсуждение группы Google). Я делаю это так:

new RestAdapter.Builder()
               .setEndpoint(LOCLSET_SERVER_URL)
               .setExecutors(AsyncTask.THREAD_POOL_EXECUTOR,
                             new MainThreadExecutor())
               .build();

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

person Kaushik Gopal    schedule 03.05.2014
comment
Не знаю, как я пропустил ответ Ника Мухина в этой теме. Это именно то, что мне нужно при использовании Espresso, Retrofit и Dagger. - person Austyn Mahoney; 06.05.2014
comment
+1 это отличное предложение, сегодня я много искал об этом. Как ни странно, мои тесты Retrofit + Espresso, которые включают асинхронные вызовы модификации, просто работают из коробки, поэтому я не знаю, покрывает ли теперь эспрессо то, как модификация работает по умолчанию? - person Dori; 25.07.2014
comment
Следует ли это добавлять только в тесты через DI или можно использовать в продакшене? - person Maragues; 03.10.2014
comment
Если вы делаете это, обязательно отключите все ваши циклические ProgressBar. Используйте indeterminateOnly=false или setVisibility(View.GONE), иначе он зависнет, а затем истечет время ожидания. - person mbmc; 08.08.2015

Если вы используете RxJava Observables с Retrofit 2.0, вы можете использовать .subscribeOn(Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR)) вместо .subscribeOn(Schedulers.io()), и все будет работать нормально!

ИЛИ, в качестве альтернативы, вы можете переопределить RxJavaSchedulersHook, что позволит вам просто внести изменения в одном месте. Например:

   public MySuperCoolClient() {

      if (BuildConfig.DEBUG) {
         configureIoSchedulerToUseAsyncTaskThreadPool();
      }

      this.restApi = new Retrofit.Builder()
              .baseUrl(Parameters.endpoint)
              .addConverterFactory(GsonConverterFactory.create(gsonBuilder()))
              .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
              .build()
              .create(RestApi.class);
   }

   private void configureIoSchedulerToUseAsyncTaskThreadPool() {
      RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() {
         @Override
         public Scheduler getIOScheduler() {
            return Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
         }
      });
   }
person I_AM_PANDA    schedule 28.04.2016

Примечание. Ответ ниже основан на Retrofit 1.6.1 — будет обновлено до последней версии. Дооснащение 1.9.0 больше не позволяет устанавливать HttpExecutor через RestAdapter.Builder

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

Настройка для обоих будет означать, что весь ваш сетевой пул ввода-вывода будет зависеть от реализации очереди aysnc, которая стала серийный номер по умолчанию для приложений с целевыми версиями ICS+

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

Выше правильно сказано, что Espresso уже цепляется за AsyncTask.THREAD_POOL_EXECUTOR. Давай ковыряться...

Как он это получает?

ThreadPoolExecutorExtractor

Кто/что этим пользуется?

BaseLayerModule имеет provideCompatAsyncTaskMonitor(ThreadPoolExecutorExtractor extractor), который возвращает AsyncTaskPoolMonitor

Как это работает? Взглянуть!

AsyncTaskPoolMonitor

Где он используется?

UiControllerImpl имеет метод loopMainThreadUntilIdle(), который вручную вызывает asyncTaskMonitor.isIdleNow() перед проверкой любых зарегистрированных пользователем idlingResources с помощью idlingResourceRegistry.allResourcesAreIdle()

Я предполагаю, что с Retrofit мы можем использовать метод RestAdapter.Builder.setExecutors(...) и передать наш собственный экземпляр (или версию) AsyncTaskPoolMonitor, используя тот же http Executor, который Retrofit инициализируется на Android с помощью

@Override Executor defaultHttpExecutor() {
      return Executors.newCachedThreadPool(new ThreadFactory() {
        @Override public Thread newThread(final Runnable r) {
          return new Thread(new Runnable() {
            @Override public void run() {
              Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
              r.run();
            }
          }, RestAdapter.IDLE_THREAD_NAME);
        }
      });
    }

(из здесь)

И заверните это в интерфейс IdlingResource для использования в наших тестах!!

Единственный вопрос в том, что поскольку Retrofit выполняет обратный вызов, используя отдельный Executor в mainThread, который опирается на основной Looper, это может привести к проблемам, но я предполагаю, что на данный момент Espresso также привязан к этому . Нужно заглянуть в этот.

person Dori    schedule 25.02.2015

Retrofit 2 использует okhttp3, который, в свою очередь, использует диспетчер. Джейк Уортон создал эту библиотеку, которая отслеживает бездействие диспетчера. Вы должны создать IdlingResource следующим образом:

IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", okHttpClient);

Имейте в виду, что этого может быть недостаточно для успешных тестов эспрессо (я пробовал), потому что IdlingResource может сказать, что он простаивает непосредственно перед или после вызова http, и ваш тест эспрессо будет выполнен и завершится ошибкой вместо ожидания.

Моя рекомендация для этих случаев — использовать пул потоков для запуска любых фоновых задач и создать IdlingResource, обертывающий этот пул потоков. Дополнительную информацию см. в этой статье: https://medium.com/@yair.kukielka/idlingresource-dagger-and-junit-rules-198e3ae791ff

person Yair Kukielka    schedule 02.08.2016

Если вы используете асинтаски, вам не нужно ничего делать, потому что Espresso уже знает, как их ждать: он использует AsyncTaskPoolMonitor, который является оболочкой для пула потоков Asynctask. .

Если вы используете собственный пул потоков (это был мой случай), вы можете использовать это класс, который будет обертывать ваш исполнитель, чтобы Espresso мог знать, когда он простаивает.

В этом замечательном посте объясняется, как это работает. Я попробовал в своем проекте, и это здорово! Используя кинжал, я получаю свой пул потоков и заворачиваю его в IdlingResource в junit @rule.

person Yair Kukielka    schedule 31.03.2016