Skip to content

Commit

Permalink
TECH: screenshot tests lesson
Browse files Browse the repository at this point in the history
  • Loading branch information
sumin93 committed Jul 24, 2023
1 parent abdf59c commit 8d7598e
Showing 1 changed file with 20 additions and 22 deletions.
42 changes: 20 additions & 22 deletions docs/Tutorial/Screenshot_tests_2.en.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
# Screenshot-тесты. Часть 2. Установка стейтов и работа с ViewModel.

Продолжаем тему screenshot-тестов.

Если в вашем приложении планируется использование screenshot-тестов, то этот момент нужно учитывать не только при написании тестов, но также при разработке приложения. В сегодняшнем уроке мы поближе познакомимся с установкой стейтов, внесем изменения в код приложения, чтобы его можно было покрыть тестами, и напишем первый скриншот тест, в котором будет работа с ViewModel.

## Предварительные знания

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

<ul>
<li>[Фрагменты](https://developer.android.com/guide/fragments) – что это, и как с ними работать</li>
<li>[ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) и шаблон проектирования MVVM</li>
<li>[StateFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)</li>
<li>[Библиотека Mockk](https://mockk.io/)</li>
</ul>
1. [Фрагменты](https://developer.android.com/guide/fragments) – что это, и как с ними работать
2. [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) и шаблон проектирования MVVM
3. [StateFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)
4. [Библиотека Mockk](https://mockk.io/)

## Обзор тестируемого приложения

Expand Down Expand Up @@ -53,7 +49,7 @@

В пакете `screenshot_tests` создаем класс `LoadUserScreenshots`

<img src="../images/screenshot_tests_2/create_class.png" alt="Create class" width="300"/>
<img src="../images/screenshot_tests_2/create_class.png" alt="Create class"/>

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

Expand Down Expand Up @@ -87,12 +83,12 @@ class LoadUserScreenshots : DocLocScreenshotTestCase(locales = "en, fr") {

Для того чтобы получить все состояния экрана мы будем, как и раньше, имитировать действия пользователя – кликать по кнопке и ждать получения результата. Создаем `PageObject` этого экрана. В пакете `com.kaspersky.kaspresso.tutorial.screen` добавляем класс `LoadUserScreen`, тип `Object`

<img src="../images/screenshot_tests_2/page_object.png" alt="Create page object" width="300"/>
<img src="../images/screenshot_tests_2/page_object.png" alt="Create page object"/>

Наследумся от `KScreen` и добавляем все необходимые UI-элементы: кнопка загрузки, ProgressBar, TextView с именем пользователя и TextView с текстом ошибки


```kotlin
```kotlin
package com.kaspersky.kaspresso.tutorial.screen

import com.kaspersky.kaspresso.screens.KScreen
Expand All @@ -111,7 +107,6 @@ object LoadUserScreen : KScreen<LoadUserScreen>() {
val username = KTextView { withId(R.id.username) }
val error = KTextView { withId(R.id.error) }
}

```
Можем создавать скриншот-тест. Добавляем метод `takeScreenshots`

Expand Down Expand Up @@ -164,7 +159,7 @@ class LoadUserScreenshots : DocLocScreenshotTestCase(locales = "en, fr") {
}

```
Далее, необходимо кликнуть по кнопку и сохранить снимок экрана в состоянии загрузки
Далее необходимо кликнуть по кнопке и сохранить снимок экрана в состоянии загрузки

```kotlin
package com.kaspersky.kaspresso.tutorial.screenshot_tests
Expand Down Expand Up @@ -266,8 +261,10 @@ class LoadUserScreenshots : DocLocScreenshotTestCase(locales = "en, fr") {
}

```
Таким образом, мы смогли написать скриншот тест, в котором получили все необходимые состояния экрана, имитируя действия пользователя – кликая по кнопке и ожидая результата выполнения запроса
Но давайте подумаем, насколько эта реализация подойдет для реальных приложений.

## Проблемы текущего подхода

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

Если мы работаем с реальным приложением, то после клика на кнопку тест будет ждать, пока запрос не вернет какой-то ответ с сервера. Если интернет будет медленным, или на сервере будут наблюдаться какие-то проблемы, то и время ожидания ответа может сильно увеличиться, соответственно будет увеличено время выполнения теста. При этом обратите внимание, что тест будет выполнен для каждой локали, которую мы передали в качестве параметра конструктора `DocLocScreenshotTestCase`, и каждый из этих тестов будет зависеть от скорости интернет-соединения и от работоспособности сервера.

Expand All @@ -290,13 +287,14 @@ class LoadUserScreenshots : DocLocScreenshotTestCase(locales = "en, fr") {
На этом этапе важно понимать паттерн MVVM (Model-View-ViewModel). Если говорить кратко, то согласно этому паттерну в приложении логика отделяется от видимой части.

Видимая часть, или можно сказать экраны (Activity и Fragments) отвечают за отображение элементов интерфейса и взаимодействия с пользователем. То есть они показывают вам какие-то элементы (кнопки, поля ввода и т.д.) и реагируют на действия пользователя (клики, свайпы и т.д). В паттерне MVVM эта часть называется View.

ViewModel в этом паттерне отвечает за логику.

Их взаимодействие выглядит следующим образом: ViewModel у себя хранит стейт экрана, она определяет, что следует показать пользователю. View получает этот стейт из ViewModel и в зависимости от полученного значения отрисовывает нужные элементы. Если пользователь выполняет какие-то действия, то View вызывает соответствующий метод из ViewModel.
Их взаимодействие выглядит следующим образом: ViewModel у себя хранит стейт экрана, она определяет, что следует показать пользователю. View получает этот стейт из ViewModel, и, в зависимости от полученного значения, отрисовывает нужные элементы. Если пользователь выполняет какие-то действия, то View вызывает соответствующий метод из ViewModel.

Давайте посмотрим пример из нашего приложения. На экране есть кнопка загрузки, пользователь кликнул по ней, View вызывает метод загрузки данных из ViewModel.

Откройте класс `LoadUserFragment` из пакета `com.kaspersky.kaspresso.tutorial.user`. Этот фрагмент представляет собой View. В следующем фрагменте кода мы устанавливаем слушатель клика на кнопку и говорим, чтобы при клике на нее был вызвать метод `loadUser` из ViewModel
Откройте класс `LoadUserFragment` из пакета `com.kaspersky.kaspresso.tutorial.user`. Этот фрагмент в паттерне MVVM представляет собой View. В следующем фрагменте кода мы устанавливаем слушатель клика на кнопку и говорим, чтобы при клике на нее был вызван метод `loadUser` из ViewModel

```kotlin
binding.loadingButton.setOnClickListener {
Expand All @@ -322,7 +320,7 @@ fun loadUser() {
}

```
View (в данном случае фрагмент `LoadUserFragment`) подписывается на стейт из ViewModel и в зависимости от полученного значения меняет содержимое экрана. Происходит это в методе observeViewModel
View (в данном случае фрагмент `LoadUserFragment`) подписывается на стейт из ViewModel и в зависимости от полученного значения меняет содержимое экрана. Происходит это в методе `observeViewModel`

```kotlin
private fun observeViewModel() {
Expand Down Expand Up @@ -642,7 +640,7 @@ if (savedInstanceState == null) {
```
А для создания фрагмента внутри скриншот-тестов будем вызывать метод `newTestInstance`.

На данном этапе в метод `onViewCreated` мы присваиваем значение переменной `viewModel` независимо от того, используется этот фрагмент для скриншот-тестов или нет. Давайте это исправим, добавим поле `isForScreenshots` типа `Boolean`, по умолчанию установим значение `false`, а в методе `newTestInstance` установим значение `true`.
На данном этапе в методе `onViewCreated` мы присваиваем значение переменной `viewModel` независимо от того, используется этот фрагмент для скриншот-тестов или нет. Давайте это исправим, добавим поле `isForScreenshots` типа `Boolean`, по умолчанию установим значение `false`, а в методе `newTestInstance` установим значение `true`.

```kotlin
package com.kaspersky.kaspresso.tutorial.user
Expand Down Expand Up @@ -686,7 +684,7 @@ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
}

```
После создания вьюмодели мы устанавливаем слушатель клика на кнопку загрузки и в этом слушателе вызываем метод вьюмодели. В случае, если мы передали замоканный вариант ViewModel, вызов этого метода приведет к падению теста, так как у нее данный метод не реализован. Поэтому вызов любых методов вьюмодели мы также будем выполнять только в том случае, если этот фрагмент используется не для скриншот-тестов:
После создания вьюмодели мы устанавливаем слушатель клика на кнопку загрузки и в этом слушателе вызываем метод вьюмодели. В случае, если мы передали замоканный вариант ViewModel, вызов этого метода `viewModel.loadUser()` приведет к падению теста, так как у нее данный метод не реализован. Поэтому вызов любых методов вьюмодели мы также будем выполнять только в том случае, если этот фрагмент используется не для скриншот-тестов:

```kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -830,15 +828,15 @@ class LoadUserScreenshots : DocLocScreenshotTestCase(locales = "en, fr") {

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

Если вы сейчас запустите тест, то увидите, что скриншоты всех состояний успешно сохраняются в папку на устройстве, и это происходит гораздо чаще, чем в предыдущем варианте теста.
Если вы сейчас запустите тест, то увидите, что скриншоты всех состояний успешно сохраняются в папку на устройстве, и это происходит гораздо быстрее, чем в предыдущем варианте теста.

## Меняем стиль

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

Данная проблема решается очень просто – в качестве параметра в метод `launchFragmentInContainer` можно передать стиль, который должен использоваться внутри фрагмента, его можно найти в манифесте приложения

<img src="../images/screenshot_tests_2/style.png" alt="Style"
<img src="../images/screenshot_tests_2/style.png" alt="Style"/>

Передать этот стиль в метод `launchFragmentInContainer` можно следующим образом:

Expand Down

0 comments on commit 8d7598e

Please sign in to comment.