Учебники

Асинхронные операции

В этой главе мы узнаем, как тестировать асинхронные операции, используя Espresso Idling Resources.

Одной из задач современного приложения является обеспечение бесперебойного взаимодействия с пользователем. Обеспечение бесперебойного взаимодействия с пользователем требует много работы в фоновом режиме, чтобы убедиться, что процесс приложения занимает не более нескольких миллисекунд. Фоновая задача варьируется от простой до дорогостоящей и сложной задачи извлечения данных из удаленного API / базы данных. Чтобы справиться с этой проблемой в прошлом, разработчик обычно писал дорогостоящую и длительную задачу в фоновом потоке и синхронизировал ее с основным UIThread после завершения фонового потока.

Если разработка многопоточного приложения сложна, то написание тестовых примеров для него еще сложнее. Например, нам не следует тестировать AdapterView до загрузки необходимых данных из базы данных. Если выборка данных выполняется в отдельном потоке, тест должен ждать, пока поток не завершится. Таким образом, среда тестирования должна синхронизироваться между фоновым потоком и потоком пользовательского интерфейса. Espresso предоставляет отличную поддержку для тестирования многопоточного приложения. Приложение использует поток следующими способами, и эспрессо поддерживает каждый сценарий.

Пользовательский интерфейс Threading

Он внутренне используется Android SDK для обеспечения удобного взаимодействия со сложными элементами пользовательского интерфейса. Эспрессо поддерживает этот сценарий прозрачно и не требует никакой настройки и специального кодирования.

Асинхронная задача

Современные языки программирования поддерживают асинхронное программирование, позволяющее выполнять легкие потоки без сложного программирования потоков. Асинхронная задача также прозрачно поддерживается платформой эспрессо.

Пользовательская тема

Разработчик может запустить новый поток для извлечения сложных или больших данных из базы данных. Чтобы поддержать этот сценарий, эспрессо предоставляет концепцию ресурсов на холостом ходу.

Давайте рассмотрим концепцию ресурса холостого хода и как это сделать в этой главе.

обзор

Концепция ресурса холостого хода очень проста и интуитивно понятна. Основная идея заключается в создании переменной (логическое значение) всякий раз, когда в отдельном потоке запускается длительный процесс, чтобы определить, запущен ли процесс или нет, и зарегистрировать его в среде тестирования. Во время тестирования тестовый исполнитель проверит зарегистрированную переменную, если таковая найдена, а затем найдет ее состояние выполнения. Если текущее состояние равно true, тестовый исполнитель будет ждать, пока статус станет ложным.

Espresso предоставляет интерфейс IdlingResources с целью поддержания рабочего состояния. Основным методом реализации является isIdleNow (). Если isIdleNow () возвращает true, espresso возобновит процесс тестирования или подождет, пока isIdleNow () вернет false. Нам нужно реализовать IdlingResources и использовать производный класс. Espresso также предоставляет некоторые встроенные реализации IdlingResources для облегчения нашей рабочей нагрузки. Они заключаются в следующем,

CountingIdlingResource

Это поддерживает внутренний счетчик запущенной задачи. Он предоставляет методы increment () и decment () . increment () добавляет единицу к счетчику, а increment () удаляет один из счетчика. isIdleNow () возвращает true только тогда, когда ни одна задача не активна.

UriIdlingResource

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

IdlingThreadPoolExecutor

Это пользовательская реализация ThreadPoolExecutor для поддержания числа активных запущенных задач в текущем пуле потоков.

IdlingScheduledThreadPoolExecutor

Это похоже на IdlingThreadPoolExecutor , но оно также планирует задачу и пользовательскую реализацию ScheduledThreadPoolExecutor.

Если в приложении используется какая-либо из вышеперечисленных реализаций IdlingResources или пользовательская, мы должны зарегистрировать ее в среде тестирования, а также до тестирования приложения с использованием класса IdlingRegistry, как показано ниже:

IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());

Более того, его можно удалить после завершения тестирования, как показано ниже:

IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());

Espresso предоставляет эту функциональность в отдельном пакете, и его необходимо настроить, как показано ниже в app.gradle.

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}

Образец заявки

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

  • Запустите Android-студию.

  • Создайте новый проект, как обсуждалось ранее, и назовите его MyIdlingFruitApp.

  • Перенесите приложение на платформу AndroidX, используя меню параметров Refactor → Migrate to AndroidX .

  • Добавьте библиотеку ресурсов эспрессо на холостом ходу в app / build.gradle (и синхронизируйте ее), как указано ниже,

Запустите Android-студию.

Создайте новый проект, как обсуждалось ранее, и назовите его MyIdlingFruitApp.

Перенесите приложение на платформу AndroidX, используя меню параметров Refactor → Migrate to AndroidX .

Добавьте библиотеку ресурсов эспрессо на холостом ходу в app / build.gradle (и синхронизируйте ее), как указано ниже,

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • Удалите дизайн по умолчанию в основной деятельности и добавьте ListView. Содержимое Activity_main.xml выглядит следующим образом:

Удалите дизайн по умолчанию в основной деятельности и добавьте ListView. Содержимое Activity_main.xml выглядит следующим образом:

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content" />
</RelativeLayout>
  • Добавьте новый ресурс макета item.xml, чтобы указать шаблон элемента представления списка. Содержимое item.xml выглядит следующим образом:

Добавьте новый ресурс макета item.xml, чтобы указать шаблон элемента представления списка. Содержимое item.xml выглядит следующим образом:

<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • Создайте новый класс — MyIdlingResource . MyIdlingResource используется для хранения нашего IdlingResource в одном месте и извлечения его при необходимости. Мы собираемся использовать CountingIdlingResource в нашем примере.

Создайте новый класс — MyIdlingResource . MyIdlingResource используется для хранения нашего IdlingResource в одном месте и извлечения его при необходимости. Мы собираемся использовать CountingIdlingResource в нашем примере.

package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.idling.CountingIdlingResource;

public class MyIdlingResource {
   private static CountingIdlingResource mCountingIdlingResource =
      new CountingIdlingResource("my_idling_resource");
   public static void increment() {
      mCountingIdlingResource.increment();
   }
   public static void decrement() {
      mCountingIdlingResource.decrement();
   }
   public static IdlingResource getIdlingResource() {
      return mCountingIdlingResource;
   }
}
  • Объявите глобальную переменную mIdlingResource типа CountingIdlingResource в классе MainActivity, как показано ниже,

Объявите глобальную переменную mIdlingResource типа CountingIdlingResource в классе MainActivity, как показано ниже,

@Nullable
private CountingIdlingResource mIdlingResource = null;
  • Напишите приватный метод получения списка фруктов из Интернета, как показано ниже,

Напишите приватный метод получения списка фруктов из Интернета, как показано ниже,

private ArrayList<String> getFruitList(String data) {
   ArrayList<String> fruits = new ArrayList<String>();
   try {
      // Get url from async task and set it into a local variable
      URL url = new URL(data);
      Log.e("URL", url.toString());
      
      // Create new HTTP connection
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      
      // Set HTTP connection method as "Get"
      conn.setRequestMethod("GET");
      
      // Do a http request and get the response code
      int responseCode = conn.getResponseCode();
      
      // check the response code and if success, get response content
      if (responseCode == HttpURLConnection.HTTP_OK) {
         BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
         String line;
         StringBuffer response = new StringBuffer();
         while ((line = in.readLine()) != null) {
            response.append(line);
         }
         in.close();
         JSONArray jsonArray = new JSONArray(response.toString());
         Log.e("HTTPResponse", response.toString());
         for(int i = 0; i < jsonArray.length(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String name = String.valueOf(jsonObject.getString("name"));
            fruits.add(name);
         }
      } else {
         throw new IOException("Unable to fetch data from url");
      }
      conn.disconnect();
   } catch (IOException | JSONException e) {
      e.printStackTrace();
   }
   return fruits;
}
  • Создайте новую задачу в методе onCreate () для извлечения данных из Интернета, используя наш метод getFruitList, после чего создайте новый адаптер и установите его в виде списка. Кроме того, уменьшите ресурс холостого хода, как только наша работа будет завершена в потоке. Код выглядит следующим образом:

Создайте новую задачу в методе onCreate () для извлечения данных из Интернета, используя наш метод getFruitList, после чего создайте новый адаптер и установите его в виде списка. Кроме того, уменьшите ресурс холостого хода, как только наша работа будет завершена в потоке. Код выглядит следующим образом:

// Get data
class FruitTask implements Runnable {
   ListView listView;
   CountingIdlingResource idlingResource;
   FruitTask(CountingIdlingResource idlingRes, ListView listView) {
      this.listView = listView;
      this.idlingResource = idlingRes;
   }
   public void run() {
      //code to do the HTTP request
      final ArrayList<String> fruitList = getFruitList("http://<your domain or IP>/fruits.json");
      try {
         synchronized (this){
            runOnUiThread(new Runnable() {
               @Override
               public void run() {
                  // Create adapter and set it to list view
                  final ArrayAdapter adapter = new
                     ArrayAdapter(MainActivity.this, R.layout.item, fruitList);
                  ListView listView = (ListView)findViewById(R.id.listView);
                  listView.setAdapter(adapter);
               }
            });
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
         MyIdlingResource.decrement(); // Set app as idle.
      }
   }
}

Здесь URL-адрес плода считается http: // <ваш домен или IP / fruits.json и форматируется как JSON. Содержание выглядит следующим образом,

[ 
   {
      "name":"Apple"
   },
   {
      "name":"Banana"
   },
   {
      "name":"Cherry"
   },
   {
      "name":"Dates"
   },
   {
      "name":"Elderberry"
   },
   {
      "name":"Fig"
   },
   {
      "name":"Grapes"
   },
   {
      "name":"Grapefruit"
   },
   {
      "name":"Guava"
   },
   {
      "name":"Jack fruit"
   },
   {
      "name":"Lemon"
   },
   {
      "name":"Mango"
   },
   {
      "name":"Orange"
   },
   {
      "name":"Papaya"
   },
   {
      "name":"Pears"
   },
   {
      "name":"Peaches"
   },
   {
      "name":"Pineapple"
   },
   {
      "name":"Plums"
   },
   {
      "name":"Raspberry"
   },
   {
      "name":"Strawberry"
   },
   {
      "name":"Watermelon"
   }
]

Примечание. Поместите файл на локальный веб-сервер и используйте его.

Теперь найдите представление, создайте новый поток, передав FruitTask , увеличьте ресурс холостого хода и, наконец, запустите задачу.

Полный код MainActivity выглядит следующим образом:

Теперь добавьте приведенную ниже конфигурацию в файл манифеста приложения, AndroidManifest.xml

Теперь скомпилируйте приведенный выше код и запустите приложение. Снимок экрана приложения My Idling Fruit выглядит следующим образом:

Теперь откройте файл ExampleInstrumentedTest.java и добавьте ActivityTestRule, как указано ниже,

Добавьте новый контрольный пример для проверки представления списка, как показано ниже,

Наконец, запустите тестовый набор, используя контекстное меню Android Studio, и проверьте, все ли тестовые примеры выполнены успешно.