Kakao — как сделать UI тестирование снова великим

    image

    В компании «Agoda» мы уделяем много внимания различным видам тестирования нашего кода. Это помогает нам гарантировать его стабильность и находить возможные проблемы продукта на раннем этапе.

    В данный момент, тестирование пользовательского интерфейса на Android для нас является небольшим персональным филиалом ада. Google предоставляет нам Espresso вместе с несколькими расширениями, UI автоматор и так далее. И хоть инструменты отлично справляются c возложенной на них задачей, делают они это не очень красиво. Когда вам нужно протестировать кейс, отличающийся от простого клика по кнопке, ваш тестовый код превращается в месиво, сложно поддающееся чтению. Просто взгляните:

    @Test
    public void espressoTest() {
      onView(allOf(allOf(withId(R.id.label_bf_hotelname), 
            isDescendantOfA(withId(R.id.custom_view_trip_review))), 
            isDescendantOfA(withId(R.id.contentView))))
            .check(matches(withEffectiveVisibility(View.VISIBLE)));
    }
    

    Для меня этот код выглядит малочитаемым. Очень сложно поддерживать работу более чем 1000 UI тестов нашего Android приложения с подобным синтаксисом. Но Google объявила официальную поддержду языка Kotlin как языка для Android разработки на конференции Google I/O 2017. Тогда то мы и подумали: «А ведь мы можем попробовать мигрировать наши тесты на Kotlin!»

    Но просто трансляция кода из Java в Kotlin не решает нашу проблему с читаемостью тестов, так как мы все еще зависим от Espresso. К счастью, Kotlin предоставляет нам множество инструментов для создания DSL. Имея это ввиду, мы представили как наши UI тесты должны выглядеть на Kotlin'е. Мы хотели, чтобы тест выше выглядел вот так:

    @Test
    fun espressoTest() {
      screen { hotelName { isVisible() } }
    }
    

    Выглядит здорово, вы так не считаете? Гораздо более читаемый и с легкостью понимаемый код, чем при использовании Espresso. И в большинстве ситуаций более компактный. Удалось ли нам добиться такого синтаксиса? Еще как!

    image

    Встречайте Kakao — простой Kotlin DSL для Android UI тестов с Espresso. Kakao предоставляет удобный синтаксис для создания UI тестов с помощью абстракции ваших Activity/Fragment или View через класс Screen.

    open class TestActivityScreen: Screen<TestActivityScreen>() {
        val content: KView = KView { withId(R.id.content) }
        val map: KView = KView { withId(R.id.map) }
        val button: KButton = KButton { withId(R.id.button) }
    
        val textViewLarge: KTextView = KTextView { 
            withId(R.id.text_view_large) 
        }
        
        val textViewSmall: KTextView = KTextView { 
            withId(R.id.text_view_small) 
        }
    }
    

    Чтобы начать использовать Kakao, вы должны включить поддержку Kotlin'а в вашем проекте. Вы можете найти всю необходимую для этого информацию на developer.android.com. Затем вам необходимо объявить вашу иерархию элементов интерфейса, используя классы, которые предоставляет библиотека. Все эти классы (KView, KTextView, KButton) — пустые классы, которые наследуют логику интерфейсов: действий (Actions) и утверждений (Assertions). Также библиотека предоставляет вспомогательные классы для создания matcher'ов в стиле DSL. Один из этих классов называется ViewBuilder и используется в конструкторах всех наследников KView.

    class KTextView : KBaseView<KTextView>, TextViewAssertions {
        constructor(function: ViewBuilder.() -> Unit): super(function)
        constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit): super(parent, function)
        constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit): super(parent, function)
    }
    

    После декларации иерархии пользовательского интерфейса, вы можете получить прямой доступ к объекту Screen и всем находящимся в нем объектам KView при помощи оператора invoke и совершать различные действия или утверждения в каскадном стиле:

    @RunWith(AndroidJUnit4::class)
    class TestActivityTest {
      @Rule
      @JvmField
      val rule = ActivityTestRule(TestActivity::class.java)
    
      val screen = TestActivityScreen()
    
      @Test
      fun test() {
        screen {
          content { isVisible() }
                
          textViewLarge {
              click()
              isVisible()
              hasAnyText()
          }
    
          textViewSmall {
              isVisible()
              hasAnyText()
          }
    
          map {
              click()
              hasAnyTag("test_tag", "non_test_tag")
          }
    
          button { hasText("BUTTON") }
        }
      }
    }
    

    Мы уже перевели большинство наших тестов на использование Kakao и очень довольны результатом. Библиотека также поддерживает более сложные конструкции, такие как RecyclerView, ListView, WebView и так далее. Также она обладает удобным подходом к кастомизации, позволяя легко создавать собственные имплементации KView с различным набором действий и утверждений, вложенными элементами и логикой.

    Мы решили выпустить этот инструмент публично, чтобы другие разработчики могли начать писать UI тесты на Kotlin'е без нужды придерживаться синтаксиса Espresso, который на наш взгляд устарел.

    Вы можете подключить Kakao в свой проект, просто добавив эту строчку в Ваш build.gradle:

    androidTestCompile 'com.agoda.kakao:kakao:1.0.0'
    

    Присоединяйтесь к нам на GitHub, оцените Kakao и давайте вместе сделаем UI тестирование снова великим! Счастливого тестирования!
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 6
    • 0
      Не понимаю, зачем делать столько конструкторов у KTextView, когда можно было сделать один дефолтный и к нему несколько фабричных фукнций? Не котлинвей получился немного на мой взгляд.
      • 0
        С конструкторами тут такая ситуация: инициализация ViewInteraction внутри класса происходит на declaration-site. И для того, чтобы пользователям не пришлось вызывать дополнительных функций, пришлось добавить 2 дополнительных конструктора, которые преобразовывают Matcher и DataInteraction в ViewInteraction.
      • 0
        Не пробовали использовать appium?
        Для меня было бы неудобно поддерживать тесты с двумя кодовыми базами под android и ios и тот же appium решает эту проблему.
        • 0
          Инструмент очень интересный, но тогда вся работа по написанию тестов ляжет исключительно на android или ios команду, следовательно разная скорость разработки. Если же разделить тесты и одни писать, к примеру на Java, а другие на Swift, то тогда кодовая база получится не консистентной.
        • 0
          А есть что-то для асинхронной работы, что-то вроде Espresso idling resource?
          • –1
            На данный момент у нас есть только функция
            idle(duration: Long = 1000)

            на уровне класса Screen. Полноценная обертка над IdlingResource у нас в планах на будущие релизы. Сейчас в приоритете Intent Extensions.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.