В этом документе объясняется, как выполнять распространенные задачи автоматического тестирования с помощью Espresso API.
API Espresso призывает авторов тестов думать о том, что может делать пользователь во время взаимодействия с приложением — находить элементы пользовательского интерфейса и взаимодействовать с ними. В то же время платформа предотвращает прямой доступ к действиям и представлениям приложения, поскольку удержание этих объектов и работа с ними вне потока пользовательского интерфейса является основным источником нестабильности тестов. Таким образом, вы не увидите такие методы, как getView()
и getCurrentActivity()
в API Espresso. Вы по-прежнему можете безопасно работать с представлениями, реализуя свои собственные подклассы ViewAction
и ViewAssertion
.
Компоненты API
К основным компонентам эспрессо относятся следующие:
- Эспрессо — точка входа для взаимодействия с представлениями (через
onView()
иonData()
). Также предоставляет API, которые не обязательно привязаны к какому-либо представлению, напримерpressBack()
. - ViewMatchers — коллекция объектов, реализующих
Matcher<? super View>
интерфейс. Вы можете передать один или несколько из них методуonView()
, чтобы найти представление в текущей иерархии представлений. - ViewActions — коллекция объектов
ViewAction
, которые можно передать методуViewInteraction.perform()
, напримерclick()
. - ViewAssertions — коллекция объектов
ViewAssertion
, которые можно передать методуViewInteraction.check()
. Большую часть времени вы будете использовать утверждение совпадений, которое использует средство сопоставления представлений для подтверждения состояния текущего выбранного представления.
Пример:
Котлин
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
Ява
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
Найти вид
В подавляющем большинстве случаев метод onView()
принимает средство сопоставления подколенных ног, которое, как ожидается, будет соответствовать одному — и только одному — представлению в текущей иерархии представлений. Сопоставители — это мощные инструменты, которые будут знакомы тем, кто использовал их с Mockito или JUnit. Если вы не знакомы с приспособлениями для подколенных сухожилий, предлагаем вам начать с беглого ознакомления с этой презентацией .
Часто желаемое представление имеет уникальный R.id
, и простое withId
сузит поиск представления. Однако во многих законных случаях вы не можете определить R.id
во время разработки теста. Например, конкретное представление может не иметь R.id
или R.id
не является уникальным. Это может сделать обычные инструментальные тесты хрупкими и усложнить их написание, поскольку обычный способ доступа к представлению — с помощью findViewById()
— не работает. Таким образом, вам может потребоваться доступ к частным членам Activity или Fragment, содержащим представление, или найти контейнер с известным R.id
и перейти к его содержимому для конкретного представления.
Espresso прекрасно решает эту проблему, позволяя вам сузить представление, используя либо существующие объекты ViewMatcher
, либо ваши собственные.
Найти представление по его R.id
так же просто, как вызвать onView()
:
Котлин
onView(withId(R.id.my_view))
Ява
onView(withId(R.id.my_view));
Иногда значения R.id
используются несколькими представлениями. В этом случае попытка использовать определенный R.id
создает исключение, например AmbiguousViewMatcherException
. Сообщение об исключении предоставляет вам текстовое представление текущей иерархии представлений, которую вы можете искать и находить представления, соответствующие неуникальному R.id
:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Просматривая различные атрибуты представлений, вы можете обнаружить уникально идентифицируемые свойства. В приведенном выше примере одно из представлений содержит текст "Hello!"
. Вы можете использовать это, чтобы сузить область поиска, используя средства сопоставления комбинаций:
Котлин
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Ява
onView(allOf(withId(R.id.my_view), withText("Hello!")));
Вы также можете не реверсировать ни одно из сопоставлений:
Котлин
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Ява
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
См. ViewMatchers
для сопоставлений представлений, предоставляемых Espresso.
Соображения
- В правильном приложении все представления, с которыми может взаимодействовать пользователь, должны либо содержать описательный текст, либо иметь описание содержимого. Дополнительные сведения см. в разделе «Как сделать приложения более доступными» . Если вы не можете сузить поиск с помощью
withText()
илиwithContentDescription()
, рассмотрите возможность рассматривать это как ошибку доступности. - Используйте наименее описательное средство сопоставления, которое находит нужное вам представление. Не переусердствуйте, так как это заставит фреймворк выполнять больше работы, чем необходимо. Например, если представление однозначно идентифицируется по его тексту, вам не нужно указывать, что представление также можно назначать из
TextView
. Для большого количества представленийR.id
представления должно быть достаточным. - Если целевое представление находится внутри
AdapterView
, напримерListView
,GridView
илиSpinner
, методonView()
может не работать. В этих случаях вместо этого вам следует использоватьonData()
.
Выполнение действия над представлением
Когда вы нашли подходящее сопоставление для целевого представления, можно выполнить для него экземпляры ViewAction
с помощью метода выполнения.
Например, чтобы нажать на представление:
Котлин
onView(...).perform(click())
Ява
onView(...).perform(click());
Вы можете выполнить более одного действия с помощью одного вызова выполнения:
Котлин
onView(...).perform(typeText("Hello"), click())
Ява
onView(...).perform(typeText("Hello"), click());
Если представление, с которым вы работаете, расположено внутри ScrollView
(вертикальное или горизонтальное), рассмотрите предыдущие действия, требующие отображения представления, такие как click()
и typeText()
, с помощью scrollTo()
. Это гарантирует, что представление будет отображено перед переходом к другому действию:
Котлин
onView(...).perform(scrollTo(), click())
Ява
onView(...).perform(scrollTo(), click());
См. ViewActions
для просмотра действий, предоставляемых Espresso.
Проверьте утверждения просмотра
Утверждения можно применить к текущему выбранному представлению с помощью метода check()
. Наиболее часто используемое утверждение — это утверждение matches()
. Он использует объект ViewMatcher
для подтверждения состояния текущего выбранного представления.
Например, чтобы проверить наличие в представлении текста "Hello!"
:
Котлин
onView(...).check(matches(withText("Hello!")))
Ява
onView(...).check(matches(withText("Hello!")));
Если вы хотите утверждать, что "Hello!"
Согласно мнению, плохой практикой считается следующее:
Котлин
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Ява
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
С другой стороны, если вы хотите утверждать, что представление с текстом "Hello!"
видим — например, после изменения флага видимости представлений — код в порядке.
Посмотреть простой тест утверждения
В этом примере SimpleActivity
содержит Button
и TextView
. При нажатии кнопки содержимое TextView
меняется на "Hello Espresso!"
.
Вот как это проверить с помощью Espresso:
Нажмите на кнопку
Первым шагом является поиск свойства, которое поможет найти кнопку. Как и ожидалось, кнопка в SimpleActivity
имеет уникальный R.id
.
Котлин
onView(withId(R.id.button_simple))
Ява
onView(withId(R.id.button_simple));
Теперь, чтобы выполнить щелчок:
Котлин
onView(withId(R.id.button_simple)).perform(click())
Ява
onView(withId(R.id.button_simple)).perform(click());
Проверьте текст TextView
TextView
с текстом для проверки также имеет уникальный R.id
:
Котлин
onView(withId(R.id.text_simple))
Ява
onView(withId(R.id.text_simple));
Теперь, чтобы проверить текст содержимого:
Котлин
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Ява
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
Проверьте загрузку данных в представлениях адаптера
AdapterView
— это особый тип виджета, который динамически загружает свои данные из адаптера. Наиболее распространенным примером AdapterView
является ListView
. В отличие от статических виджетов, таких как LinearLayout
, в текущую иерархию представлений может быть загружено только подмножество дочерних элементов AdapterView
. Простой поиск onView()
не обнаружит представления, которые в данный момент не загружены.
Espresso решает эту проблему, предоставляя отдельную точку входа onData()
, которая может сначала загрузить рассматриваемый элемент адаптера, поместив его в фокус перед работой с ним или любым из его дочерних элементов.
Предупреждение. Пользовательские реализации AdapterView
могут иметь проблемы с методом onData()
, если они нарушают контракты наследования, особенно API getItem()
. В таких случаях лучший вариант действий — провести рефакторинг кода приложения. Если вы не можете этого сделать, вы можете реализовать соответствующий собственный AdapterViewProtocol
. Для получения дополнительной информации ознакомьтесь с классом AdapterViewProtocols
по умолчанию, предоставляемым Espresso.
Простой тест представления адаптера
Этот простой тест демонстрирует, как использовать onData()
. SimpleActivity
содержит Spinner
с несколькими элементами, обозначающими типы кофейных напитков. Когда элемент выбран, TextView
меняется на "One %sa day!"
, где %s
представляет выбранный элемент.
Цель этого теста — открыть Spinner
, выбрать определенный элемент и убедиться, что TextView
содержит этот элемент. Поскольку класс Spinner
основан на AdapterView
, для сопоставления элемента рекомендуется использовать onData()
вместо onView()
.
Открыть выбор товара
Котлин
onView(withId(R.id.spinner_simple)).perform(click())
Ява
onView(withId(R.id.spinner_simple)).perform(click());
Выберите элемент
Для выбора элемента Spinner
создает ListView
с его содержимым. Это представление может быть очень длинным, и элемент может не быть включен в иерархию представлений. Используя onData()
мы помещаем нужный элемент в иерархию представлений. Элементы в Spinner
являются строками, поэтому мы хотим сопоставить элемент, равный строке "Americano"
:
Котлин
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Ява
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
Проверьте правильность текста
Котлин
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Ява
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
Отладка
Espresso предоставляет полезную информацию для отладки в случае неудачного теста:
Ведение журнала
Espresso записывает все действия просмотра в logcat. Например:
ViewInteraction: Performing 'single click' action on view with text: Espresso
Просмотр иерархии
Espresso печатает иерархию представлений в сообщении об исключении при сбое onView()
.
- Если
onView()
не находит целевое представление, выдается исключениеNoMatchingViewException
. Вы можете проверить иерархию представлений в строке исключения, чтобы проанализировать, почему средство сопоставления не соответствует ни одному представлению. - Если
onView()
находит несколько представлений, соответствующих данному сопоставителю, генерируется исключениеAmbiguousViewMatcherException
. Иерархия представлений распечатывается, и все сопоставленные представления помечаются меткойMATCHES
:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
При работе со сложной иерархией представлений или неожиданным поведением виджетов всегда полезно использовать для объяснения средство просмотра иерархии в Android Studio.
Предупреждения в представлении адаптера
Espresso предупреждает пользователей о наличии виджетов AdapterView
. Когда операция onView()
генерирует исключение NoMatchingViewException
и виджеты AdapterView
присутствуют в иерархии представлений, наиболее распространенным решением является использование onData()
. Сообщение об исключении будет содержать предупреждение со списком представлений адаптера. Вы можете использовать эту информацию для вызова onData()
для загрузки целевого представления.
Дополнительные ресурсы
Для получения дополнительной информации об использовании Espresso в тестах Android обратитесь к следующим ресурсам.
Образцы
- CustomMatcherSample : показывает, как расширить Espresso для соответствия свойству подсказки объекта
EditText
. - RecyclerViewSample : действия
RecyclerView
для Espresso. - (более...)