Написание автоматизированных тестов с помощью UI Automator

UI Automator — это платформа тестирования пользовательского интерфейса, подходящая для функционального тестирования пользовательского интерфейса между приложениями в системе и установленных приложениях. API-интерфейсы UI Automator позволяют взаимодействовать с видимыми элементами на устройстве независимо от того, какое Activity находится в фокусе, поэтому они позволяют выполнять такие операции, как открытие меню «Настройки» или средства запуска приложений на тестовом устройстве. Ваш тест может искать компонент пользовательского интерфейса, используя удобные дескрипторы, такие как текст, отображаемый в этом компоненте, или описание его содержимого.

Платформа тестирования UI Automator представляет собой API на основе инструментов и работает с средством запуска тестов AndroidJUnitRunner . Он хорошо подходит для написания непрозрачных автоматических тестов в стиле коробок, где тестовый код не зависит от деталей внутренней реализации целевого приложения.

Ключевые особенности среды тестирования UI Automator включают в себя следующее:

Доступ к состоянию устройства

Платформа тестирования UI Automator предоставляет класс UiDevice для доступа и выполнения операций на устройстве, на котором работает целевое приложение. Вы можете вызвать его методы для доступа к свойствам устройства, таким как текущая ориентация или размер дисплея. Класс UiDevice также позволяет выполнять следующие действия:

  1. Измените поворот устройства.
  2. Нажимайте аппаратные клавиши, например «громкость вверх».
  3. Нажмите кнопки «Назад», «Домой» или «Меню».
  4. Откройте панель уведомлений.
  5. Сделайте снимок экрана текущего окна.

Например, чтобы имитировать нажатие кнопки «Домой», вызовите метод UiDevice.pressHome() .

API-интерфейсы автоматизатора пользовательского интерфейса

API-интерфейсы UI Automator позволяют писать надежные тесты без необходимости знать детали реализации целевого приложения. Вы можете использовать эти API для захвата и управления компонентами пользовательского интерфейса в нескольких приложениях:

  • UiObject2 : представляет элемент пользовательского интерфейса, видимый на устройстве.
  • BySelector : определяет критерии соответствия элементов пользовательского интерфейса.
  • By : Конструирует BySelector в краткой форме.
  • Configurator : позволяет установить ключевые параметры для запуска тестов UI Automator.

Например, следующий код показывает, как написать тестовый сценарий, открывающий приложение Gmail на устройстве:

Котлин

device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.pressHome()

val gmail: UiObject2 = device.findObject(By.text("Gmail"))
// Perform a click and wait until the app is opened.
val opened: Boolean = gmail.clickAndWait(Until.newWindow(), 3000)
assertThat(opened).isTrue()

Ява

device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressHome();

UiObject2 gmail = device.findObject(By.text("Gmail"));
// Perform a click and wait until the app is opened.
Boolean opened = gmail.clickAndWait(Until.newWindow(), 3000);
assertTrue(opened);

Настройка UI Automator

Прежде чем создавать тест пользовательского интерфейса с помощью UI Automator, обязательно настройте расположение исходного кода теста и зависимости проекта, как описано в разделе Настройка проекта для AndroidX Test .

В файле build.gradle вашего модуля приложения Android вы должны установить ссылку на зависимость для библиотеки UI Automator:

Котлин

dependencies {
  ...
  androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0-alpha03')
}

классный

dependencies {
  ...
  androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha03'
}

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

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

Прежде чем разрабатывать тест, проверьте компоненты пользовательского интерфейса, видимые на устройстве. Чтобы гарантировать, что ваши тесты UI Automator могут получить доступ к этим компонентам, убедитесь, что эти компоненты имеют видимые текстовые метки, значения android:contentDescription или и то, и другое.

Инструмент uiautomatorviewer предоставляет удобный визуальный интерфейс для проверки иерархии макета и просмотра свойств компонентов пользовательского интерфейса, видимых на переднем плане устройства. Эта информация позволяет создавать более детальные тесты с помощью UI Automator. Например, вы можете создать селектор пользовательского интерфейса, соответствующий определенному видимому свойству.

Чтобы запустить инструмент uiautomatorviewer :

  1. Запустите целевое приложение на физическом устройстве.
  2. Подключите устройство к вашей машине разработки.
  3. Откройте окно терминала и перейдите в каталог <android-sdk>/tools/ .
  4. Запустите инструмент с помощью этой команды:
 $ uiautomatorviewer

Чтобы просмотреть свойства пользовательского интерфейса вашего приложения:

  1. В интерфейсе uiautomatorviewer нажмите кнопку «Снимок экрана устройства» .
  2. Наведите указатель мыши на снимок на левой панели, чтобы увидеть компоненты пользовательского интерфейса, идентифицированные инструментом uiautomatorviewer . Свойства перечислены в нижней правой панели, а иерархия макета — в верхней правой панели.
  3. При необходимости нажмите кнопку «Переключить узлы NAF», чтобы просмотреть компоненты пользовательского интерфейса, недоступные для UI Automator. Для этих компонентов может быть доступна только ограниченная информация.

Чтобы узнать об распространенных типах компонентов пользовательского интерфейса, предоставляемых Android, см. Пользовательский интерфейс .

Убедитесь, что ваша деятельность доступна

Платформа тестирования UI Automator лучше работает с приложениями, в которых реализованы специальные возможности Android. Когда вы используете элементы пользовательского интерфейса типа View или подкласс View из SDK, вам не нужно реализовывать поддержку специальных возможностей, поскольку эти классы уже сделали это за вас.

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

  1. Создайте конкретный класс, расширяющий ExploreByTouchHelper .
  2. Свяжите экземпляр нового класса с определенным пользовательским элементом пользовательского интерфейса, вызвав setAccessibilityDelegate() .

Дополнительные рекомендации по добавлению специальных возможностей к элементам настраиваемого представления см. в разделе Создание доступных настраиваемых представлений . Дополнительные сведения об общих рекомендациях по обеспечению специальных возможностей на Android см. в разделе «Как сделать приложения более доступными» .

Создайте тестовый класс UI Automator.

Ваш тестовый класс UI Automator должен быть написан так же, как тестовый класс JUnit 4. Дополнительные сведения о создании классов тестов JUnit 4 и использовании утверждений и аннотаций JUnit 4 см. в разделе Создание класса инструментированного модульного теста .

Добавьте аннотацию @RunWith(AndroidJUnit4.class) в начало определения тестового класса. Вам также необходимо указать класс AndroidJUnitRunner , предоставленный в AndroidX Test, в качестве средства запуска тестов по умолчанию. Этот шаг более подробно описан в разделе «Выполнение тестов UI Automator на устройстве или эмуляторе» .

Реализуйте следующую модель программирования в тестовом классе UI Automator:

  1. Получите объект UiDevice для доступа к устройству, которое вы хотите протестировать, вызвав метод getInstance() и передав ему объект Instrumentation в качестве аргумента.
  2. Получите объект UiObject2 для доступа к компоненту пользовательского интерфейса, отображаемому на устройстве (например, к текущему представлению на переднем плане), вызвав метод findObject() .
  3. Имитируйте конкретное взаимодействие пользователя с этим компонентом пользовательского интерфейса, вызывая метод UiObject2 ; например, вызовите метод ScrollUntil() для прокрутки и метод setText() для редактирования текстового поля. При необходимости вы можете повторно вызывать API-интерфейсы на шагах 2 и 3, чтобы протестировать более сложные взаимодействия с пользователем, включающие несколько компонентов пользовательского интерфейса или последовательности действий пользователя.
  4. Убедитесь, что пользовательский интерфейс отражает ожидаемое состояние или поведение после выполнения этих взаимодействий с пользователем.

Эти шаги более подробно описаны в разделах ниже.

Доступ к компонентам пользовательского интерфейса

Объект UiDevice — это основной способ доступа к состоянию устройства и управления им. В своих тестах вы можете вызывать методы UiDevice для проверки состояния различных свойств, таких как текущая ориентация или размер дисплея. Ваш тест может использовать объект UiDevice для выполнения действий на уровне устройства, таких как принудительное вращение устройства, нажатие аппаратных кнопок D-pad и нажатие кнопок «Домой» и «Меню».

Рекомендуется начинать тестирование с главного экрана устройства. На главном экране (или в другом исходном месте, выбранном вами на устройстве) вы можете вызывать методы, предоставляемые API-интерфейсом UI Automator, для выбора и взаимодействия с определенными элементами пользовательского интерфейса.

В следующем фрагменте кода показано, как ваш тест может получить экземпляр UiDevice и имитировать нажатие кнопки «Домой»:

Котлин

import org.junit.Before
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
...

private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"
private const val LAUNCH_TIMEOUT = 5000L
private const val STRING_TO_BE_TYPED = "UiAutomator"

@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = 18)
class ChangeTextBehaviorTest2 {

private lateinit var device: UiDevice

@Before
fun startMainActivityFromHomeScreen() {
  // Initialize UiDevice instance
  device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

  // Start from the home screen
  device.pressHome()

  // Wait for launcher
  val launcherPackage: String = device.launcherPackageName
  assertThat(launcherPackage, notNullValue())
  device.wait(
    Until.hasObject(By.pkg(launcherPackage).depth(0)),
    LAUNCH_TIMEOUT
  )

  // Launch the app
  val context = ApplicationProvider.getApplicationContextC<ontext(>)
  val intent = context.packageManager.getLaunchIntentForPackage(
  BASIC_SAMPLE_PACKAGE).apply {
    // Clear out any previous instances
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
  }
  context.startActivity(intent)

  // Wait for the app to appear
  device.wait(
    Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
    LAUNCH_TIMEOUT
    )
  }
}

Ява

import org.junit.Before;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
...

@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {

  private static final String BASIC_SAMPLE_PACKAGE
  = "com.example.android.testing.uiautomator.BasicSample";
  private static final int LAUNCH_TIMEOUT = 5000;
  private static final String STRING_TO_BE_TYPED = "UiAutomator";
  private UiDevice device;

  @Before
  public void startMainActivityFromHomeScreen() {
    // Initialize UiDevice instance
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

    // Start from the home screen
    device.pressHome();

    // Wait for launcher
    final String launcherPackage = device.getLauncherPackageName();
    assertThat(launcherPackage, notNullValue());
    device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
    LAUNCH_TIMEOUT);

    // Launch the app
    Context context = ApplicationProvider.getApplicationContext();
    final Intent intent = context.getPackageManager()
    .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
    // Clear out any previous instances
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    context.startActivity(intent);

    // Wait for the app to appear
    device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
    LAUNCH_TIMEOUT);
    }
}

В этом примере оператор @SdkSuppress(minSdkVersion = 18) помогает гарантировать, что тесты будут выполняться только на устройствах с Android 4.3 (уровень API 18) или выше, как того требует платформа UI Automator.

Используйте метод findObject() для получения UiObject2 , который представляет представление, соответствующее заданным критериям селектора. При необходимости вы можете повторно использовать экземпляры UiObject2 , созданные вами в других частях тестирования вашего приложения. Обратите внимание, что платформа тестирования UI Automator ищет соответствие в текущем отображении каждый раз, когда ваш тест использует экземпляр UiObject2 для щелчка по элементу пользовательского интерфейса или запроса свойства.

В следующем фрагменте показано, как ваш тест может создавать экземпляры UiObject2 , представляющие кнопки «Отмена» и «ОК» в приложении.

Котлин

val okButton: UiObject2 = device.findObject(
    By.text("OK").clazz("android.widget.Button")
)

// Simulate a user-click on the OK button, if found.
if (okButton != null) {
    okButton.click()
}

Ява

UiObject2 okButton = device.findObject(
    By.text("OK").clazz("android.widget.Button")
);

// Simulate a user-click on the OK button, if found.
if (okButton != null) {
    okButton.click();
}

Укажите селектор

Если вы хотите получить доступ к определенному компоненту пользовательского интерфейса в приложении, используйте класс By для создания экземпляра BySelector . BySelector представляет собой запрос для определенных элементов отображаемого пользовательского интерфейса.

Если найдено более одного соответствующего элемента, первый соответствующий элемент в иерархии макета возвращается как целевой UiObject2 . При создании BySelector вы можете объединить несколько свойств для уточнения поиска. Если соответствующий элемент пользовательского интерфейса не найден, возвращается null .

Вы можете использовать метод hasChild() или hasDescendant() для вложения нескольких экземпляров BySelector . Например, в следующем примере кода показано, как ваш тест может указать поиск для поиска первого ListView , имеющего дочерний элемент пользовательского интерфейса со свойством text.

Котлин

val listView: UiObject2 = device.findObject(
    By.clazz("android.widget.ListView")
        .hasChild(
            By.text("Apps")
        )
)

Ява

UiObject2 listView = device.findObject(
    By.clazz("android.widget.ListView")
        .hasChild(
            By.text("Apps")
        )
);

Может быть полезно указать состояние объекта в критериях выбора. Например, если вы хотите выбрать список всех отмеченных элементов, чтобы их можно было снять, вызовите метод checked() с аргументом, установленным в true.

Выполнять действия

После того как ваш тест получил объект UiObject2 , вы можете вызвать методы класса UiObject2 для выполнения взаимодействия с пользователем с компонентом пользовательского интерфейса, представленным этим объектом. Вы можете указать такие действия как:

  • click() : щелкает по центру видимых границ элемента пользовательского интерфейса.
  • drag() : перетаскивает этот объект в произвольные координаты.
  • setText() : устанавливает текст в редактируемое поле после очистки содержимого поля. И наоборот, методclear clear() очищает существующий текст в редактируемом поле.
  • swipe() : выполняет действие смахивания в указанном направлении.
  • scrollUntil() : выполняет действие прокрутки в указанном направлении до тех пор, пока не будет выполнено Condition или EventCondition .

Платформа тестирования UI Automator позволяет отправлять намерение или запускать действие без использования команд оболочки, получая объект Context через getContext() .

В следующем фрагменте показано, как ваш тест может использовать намерение для запуска тестируемого приложения. Этот подход полезен, когда вас интересует только тестирование приложения-калькулятора и вас не интересует программа запуска.

Котлин

fun setUp() {
...

  // Launch a simple calculator app
  val context = getInstrumentation().context
  val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
  }
  // Clear out any previous instances
  context.startActivity(intent)
  device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT)
}

Ява

public void setUp() {
...

  // Launch a simple calculator app
  Context context = getInstrumentation().getContext();
  Intent intent = context.getPackageManager()
  .getLaunchIntentForPackage(CALC_PACKAGE);
  intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

  // Clear out any previous instances
  context.startActivity(intent);
  device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}

Проверьте результаты

InstrumentationTestCase расширяет TestCase , поэтому вы можете использовать стандартные методы JUnit Assert для проверки того, что компоненты пользовательского интерфейса в приложении возвращают ожидаемые результаты.

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

Котлин

private const val CALC_PACKAGE = "com.myexample.calc"

fun testTwoPlusThreeEqualsFive() {
  // Enter an equation: 2 + 3 = ?
  device.findObject(By.res(CALC_PACKAGE, "two")).click()
  device.findObject(By.res(CALC_PACKAGE, "plus")).click()
  device.findObject(By.res(CALC_PACKAGE, "three")).click()
  device.findObject(By.res(CALC_PACKAGE, "equals")).click()

  // Verify the result = 5
  val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result"))
  assertEquals("5", result.text)
}

Ява

private static final String CALC_PACKAGE = "com.myexample.calc";

public void testTwoPlusThreeEqualsFive() {
  // Enter an equation: 2 + 3 = ?
  device.findObject(By.res(CALC_PACKAGE, "two")).click();
  device.findObject(By.res(CALC_PACKAGE, "plus")).click();
  device.findObject(By.res(CALC_PACKAGE, "three")).click();
  device.findObject(By.res(CALC_PACKAGE, "equals")).click();

  // Verify the result = 5
  UiObject2 result = device.findObject(By.res(CALC_PACKAGE, "result"));
  assertEquals("5", result.getText());
}

Запускайте тесты UI Automator на устройстве или эмуляторе.

Вы можете запускать тесты UI Automator из Android Studio или из командной строки. Обязательно укажите AndroidJUnitRunner в качестве средства запуска инструментов по умолчанию в вашем проекте.

Больше примеров

Взаимодействие с системным пользовательским интерфейсом

UI Automator может взаимодействовать со всем на экране, включая системные элементы за пределами вашего приложения, как показано в следующих фрагментах кода:

Котлин

// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.executeShellCommand("am start -a android.settings.SETTINGS")

Ява

// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.executeShellCommand("am start -a android.settings.SETTINGS");

Котлин

// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openNotification()

Ява

// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openNotification();

Котлин

// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openQuickSettings()

Ява

// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openQuickSettings();

Котлин

// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"))
print(clock.getText())

Ява

// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"));
print(clock.getText());

Ждите переходов

Выключить беспокойство
Рисунок 1. UI Automator отключает режим «Не беспокоить» на тестовом устройстве.

Переходы между экранами могут занять некоторое время, и прогнозировать их продолжительность ненадежно, поэтому после выполнения операций следует подождать с UI Automator. UI Automator предоставляет для этого несколько методов:

В следующем фрагменте кода показано, как использовать UI Automator для отключения режима «Не беспокоить» в настройках системы с помощью метода performActionAndWait() , который ожидает переходов:

Котлин

@Test
@SdkSuppress(minSdkVersion = 21)
@Throws(Exception::class)
fun turnOffDoNotDisturb() {
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
    device.performActionAndWait({
        try {
            device.executeShellCommand("am start -a android.settings.SETTINGS")
        } catch (e: IOException) {
            throw RuntimeException(e)
        }
    }, Until.newWindow(), 1000)
    // Check system settings has been opened.
    Assert.assertTrue(device.hasObject(By.pkg("com.android.settings")))

    // Scroll the settings to the top and find Notifications button
    var scrollableObj: UiObject2 = device.findObject(By.scrollable(true))
    scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP))
    val notificationsButton = scrollableObj.findObject(By.text("Notifications"))

    // Click the Notifications button and wait until a new window is opened.
    device.performActionAndWait({ notificationsButton.click() }, Until.newWindow(), 1000)
    scrollableObj = device.findObject(By.scrollable(true))
    // Scroll down until it finds a Do Not Disturb button.
    val doNotDisturb = scrollableObj.scrollUntil(
        Direction.DOWN,
        Until.findObject(By.textContains("Do Not Disturb"))
    )
    device.performActionAndWait({ doNotDisturb.click() }, Until.newWindow(), 1000)
    // Turn off the Do Not Disturb.
    val turnOnDoNotDisturb = device.findObject(By.text("Turn on now"))
    turnOnDoNotDisturb?.click()
    Assert.assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000))
}

Ява

@Test
@SdkSuppress(minSdkVersion = 21)
public void turnOffDoNotDisturb() throws Exception{
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    device.performActionAndWait(() - >{
        try {
            device.executeShellCommand("am start -a android.settings.SETTINGS");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }, Until.newWindow(), 1000);
    // Check system settings has been opened.
    assertTrue(device.hasObject(By.pkg("com.android.settings")));

    // Scroll the settings to the top and find Notifications button
    UiObject2 scrollableObj = device.findObject(By.scrollable(true));
    scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP));
    UiObject2 notificationsButton = scrollableObj.findObject(By.text("Notifications"));

    // Click the Notifications button and wait until a new window is opened.
    device.performActionAndWait(() - >notificationsButton.click(), Until.newWindow(), 1000);
    scrollableObj = device.findObject(By.scrollable(true));
    // Scroll down until it finds a Do Not Disturb button.
    UiObject2 doNotDisturb = scrollableObj.scrollUntil(Direction.DOWN,
            Until.findObject(By.textContains("Do Not Disturb")));
    device.performActionAndWait(()- >doNotDisturb.click(), Until.newWindow(), 1000);
    // Turn off the Do Not Disturb.
    UiObject2 turnOnDoNotDisturb = device.findObject(By.text("Turn on now"));
    if(turnOnDoNotDisturb != null) {
        turnOnDoNotDisturb.click();
    }
    assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000));
}

Дополнительные ресурсы

Для получения дополнительной информации об использовании UI Automator в тестах Android обратитесь к следующим ресурсам.

Справочная документация:

Образцы

  • BasicSample : базовый пример Automator пользовательского интерфейса.