Приятное тестирование с Espresso

  • Tutorial
image
Хочу представить вашему вниманию библиотеку для тестирования Android приложений от Google. Espresso была анонсирована 23 апреля прошлого года на Google Test Automation Conference 2013. В октябре прошлого года она стала официально Open Source проектом. Что же из себя представляет эта библиотека, мы рассмотрим ниже.

GoogleInstrumentationTestRunner


GoogleInstrumentationTestRunner, наследник стандартного InstrumentTestRunner, который был улучшен. Одно из самых главных его достоинств, то что он гарантирует выполнение теста после запуска и старта приложения. Другими словами, он гарантирует что все тесты будут стартоваться после выполнения onCreate (если это условие не выполняется то это может привести к неожиданным исключениям). Так же гарантирует что все запущенные активити завершаться до окончания отработки GoogleInstrumentationTestRunner. Поддерживает тесты начиная с Api 8.

Версия 1.1


8 января 2014 вышла новая версия. Что нового:
  • Включена поддержка свайпов. Это очень хорошее дополнение, так как до этого нужно было писать свой кастомный обработчик;
  • Поддержка multi-window;
  • Исправлены баги.

От автора


Прежде чем переходить от теории к практике, хочется поделится своими впечатлениями. Библиотека очень молодая, ей нету ещё и года. Но работает таки стабильно и выходила она сразу с версии 1.0, что уже говорит о том что она вышла не просто ради бета/альфы теста. Писать с её помощью тесты очень приятно, так как много функционала она берёт на себя. Например, не нужно делать фокус на кнопке, проверять доступна ли она на экране и т.д., с Espresso вам достаточно обратится к кнопке по её id или text/description и Espresso уже всё сделает за вас, если кнопка не видна или не доступна на экране то тест автоматом уже не пройдёт. Они действительно сделали большой упор на UI.
Парни с тех поддержки (они же и разработчики сего чуда) стараются максимально быстро и развернуто давать ответы на ваши вопросы. Мне довелось немножко с ними пообщаться. Так вот, самый главный вопрос который им задавали «Чем Espresso лучше Robotium?». Они говорят, что Robotium действительно хороша, но самой главной причиной, по которой они решили написать свой фреймворк это скорость выполнения тестов и их не всегда стабильность. Они уверяют что Espresso работает намного быстрее чем другие фреймворки (хотя цифры не приводят, я постараюсь в ближайшее время сделать сравнения). Но ещё одна не мало важная особенность, это то что Espresso использует синхронизацию с Main Thread. Instrumentation запускается в отдельном потоке, без синхронизации тестов с обновлением UI возникают проблемы со скоростью и могут возникнуть критические ошибки в самих тестах. Большинство разработчиков игнорируют этот факт и ставят задержки в потоке (sleep), что есть уже не правильно. С Espresso вам этого делать уже не надо будет. Robotium в свою очередь не использует синхронизацию, что влечёт за собой присыпания потоков и медленное выполнение тестов. Изначально все приложения Google тестировались при помощи Robotium, потому что в тот момент не было времени писать свой инструментал. Покрыть тестами свои проекты вы сможете до 95%!
Ещё была ситуация когда мне понадобилось запустить тестовый проект в своем Application. Казалось бы, всё просто, переопределил стандартный, подставил в манифест и всё. Но как оказалось тесты все работают в том же Application что и приложение. Понимаю, что это логично, но вот такая реализация оказалась не возможной.

От теории к практики


Основные классы:
  • Espresso – основной класс. Содержит в себе статические методы, такие как нажатия на системные кнопки (back, home), вызвать/спрятать клавиатуру, открыть меню, обратится к View;
  • ViewMatchers – коллекция объектов для нахождения View на экране;
  • ViewActions – используется для обработчиков View (click, longClick, doubleClick, swipe, scroll и т.д.);

На Espresso стоит переходить хотя бы за её простоту. Работа с View пишется в одну строчку. Напишем простой пример для ввода данных в EditText и нажатие на кнопку:
	@SmallTest
	public void testSendMessageUI()
	{
		Espresso.onView(ViewMatchers.withId(com.eleks.espresso.example.app.R.id.etEmail)).perform(ViewActions.typeText("test@test.com"));
		Espresso.onView(ViewMatchers.withId(com.eleks.espresso.example.app.R.id.etMessage)).perform(ViewActions.typeText("Espresso"));
		Espresso.onView(ViewMatchers.withId(com.eleks.espresso.example.app.R.id.btnSend)).perform(ViewActions.click());
	}


Всё просто. Ищется View на экране при помощи ViewMatchers по id, вводится текст имитируя человеческий ввод. Далее находится кнопка и происходит событие onClick по ней. Попробуйте спрятать кнопку и вы увидите, что тест не пройдёт так как Espresso просто не найдёт её на экране.

	@SmallTest
	public void testOpenNavigationDrawer()
	{
		Espresso.onView(ViewMatchers.withId(com.eleks.espresso.example.app.R.id.content_frame)).perform(ViewActions.swipeRight());
		ListView lvDrawerMenu = (ListView) getActivity().findViewById(com.eleks.espresso.example.app.R.id.lvDrawerMenu);
		Preconditions.checkNotNull(lvDrawerMenu, "lvDrawerMenu is null");
		final int count = lvDrawerMenu.getAdapter().getCount();
		Preconditions.checkPositionIndex(2, count, "No 1 index " + count + " size");
		Object obj = lvDrawerMenu.getItemAtPosition(2);
		Espresso.onView(Matchers.allOf(ViewMatchers.withId(com.eleks.espresso.example.app.R.id.tvItem), ViewMatchers.hasSibling(ViewMatchers.withText(obj.toString())))).perform(ViewActions.click());
	}

В этом примере делается свайп вправо и открывается NavigationDrawer. Далее в происходит событие onClick по третьей ячейке листа. Класс Preconditions используется для проверки входных данных. Им удобно проверять null, выход за границы массива, проверка позиции, проверка значения и т.д. Далее мы ищем любую View с значением, которое мы предварительно вытянули с листа.
Для более лучшей надёжности и совместимости тестов, разработчики Espresso рекомендуют метод onView и класс ViewMatcher который сам ищет View на активити, вместо привычной нам findViewById. Но в таком случае приходится писать кастомные обработчики и методы, так как если мы найдём View при помощи ViewMatcher, то багаж методов которые мы можем вызвать будет ограничен. Но в любом случае Espresso движется в правильном направлении и её разработчики пытаются сделать так что бы написание тестов было как можно более приятным.
Example на Github
Где скачать Espresso

Only registered users can participate in poll. Log in, please.

Какие инструменты вы используете для автоматизации тестирования?

  • 29.9%Robotium66
  • 12.7%UI Automator28
  • 38.0%Espresso84
  • 18.1%Monkey testing40
  • 21.3%Другое47
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 15

    +2
    Хм. А это точно про юнит-тесты? Больше похоже на функциональные
      +1
      Про функциональные, уже подправил. В терминалогии иногда путаюсь.
        0
        Друзья, используйте терминологию Google чтобы никогда не путаться. У автора статьи в примерах исходного кода она фигурирует. googletesting.blogspot.ru/2010/12/test-sizes.html
      0
      Что ж они на developer.android.com о ней молчат. А я на днях начал подобный велосипед для тестов делать — bitbucket.org/zserge/lemur
      Работает через те же механизмы, что и глючный UI Automator, но намного проще. Тестовые скрипты пишутся на Lua:

      lemur.find('./EditText/').text('user@example.com')
      lemur.find('.android.widget.Button', 'Login').click()
      assert(lemur.find(./Progress/), 'Progress is not shown')
      


      Вообщем как и UI Automator, позволяет искать UI элементы по тексту, классу (в том числе через regexp) и номеру внутри родительского элемента, делать с ними простейшие действия типа кликов, свайпов, нажатий кнопок, ввода текста. Вроде все просто, а времени до ума довести не хватает.
        +1
        Кстати нужно будет ещё в исходниках покопаться, потому что везде в примерах view ищеться по тексту, а не по id. Ведь по идеи по id поиск должен происходить быстрее.
          0
          Быстренький grep нашел вот это (ViewMatchersjava):

          public static Matcher<View> withId(final Matcher<Integer> integerMatcher) {...} 
          

          Значит умеет по id искать. Внутри как раз view.getId() сравнивается.
        0
        На картинке не эспрессо.
          +2
          Самое главное замечание. Так и знал что нужно было картинку с котиками ставить!
            0
            Дьявол кроется в деталях
          +1
          Использовали Robotium. Хорош, но не стабилен. И не сказать, что прям быстр.

          Сейчас изучаем новые фреймворки: MonkeyTalk и SOASTA TouchTest.
          Моя задача как раз изучить последний, поэтому расскажу о нем немного поподробней.

          SOASTA TouchTest.
          + скорость выполнения тестов = скорости нажатий на кнопочки
          + не нужен adb
          + высокая стабильность. полировка практически отсутствует за отсутствием необходимости
          + тестеры вообще могут не копаться в коде, садитесь и пишите как говорится
          + стремящееся к нулю количество необходимых изменений в проекте. перед тестированием запускается специальная утилита, которая сделает все за вас (подправит манифест, добавит билд конфигурации и библиотеки и тп)
          + возможность сборки testable проекта как и из Eclipse, так и средствами Ant

          ± whitebox testing. по факту не предусмотрено. Но, есть такая вещь как App Internal Value, с помощью которой можно получить необходимое значение чего угодно. Договариваетесь (с самим собой, с командой), за какими «переменными» будет на самом деле выполняться ваш самописный код при обращении к этим переменным и вы решили проблему хоть частично. Например, у нас так осуществляется деавторизация приложения (не через UI же делать)
          ± не нужно писать код, описывающий тест (исключение — конструкции на js), но и практически невозможно, если появится необходимость (это xml, вспомните ваши build.xml'и, здесь все еще хуже). Пишется исключительно манипуляциями с самим приложением.
            0
            Мне одному кажется, что:
            Object obj = lvDrawerMenu.getItemAtPosition(2);
            onView(withText(obj.toString())).perform(click());
            
            довольно уродливый код?
            Брать текст из объекта только что бы найти 2-й элемент в меню, как то дико.
            Можно ли решить эту задачу используя только Espresso?
              0
              Спасибо за томик! Есть вопрос, умеет ли Espresso находить View по заданному ресурсу, скажем ImageButton с заданным drawable для него? В данный момент используем Robotium и он к сожалению этого делать не умеет и разработчики ответили что не планируется, к сожалению.
                0
                На сколько я помню то не умеет. Я в итоге отказался от использования Espresso в новых проектов. Были заморочки с подключением в АС. Вот сейчас посмотрел то они уже почти год как не меняли ничего. По ходу проект заброшен. Так что я использую Robotium и не советую с него слазить.
                  0
                  Забавно читать такие комменты из будущего, 2019 :)

              Only users with full accounts can post comments. Log in, please.