1 added
1 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Page Object Model (POM) в тестировании - концепция не новая. И если в одних случаях она целесообразна, то в других приводит к дополнительным расходам. В этой статье мы рассмотрим основные механизмы, лежащие в основе POM. Для этого создадим базовую среду тестирования<strong>Espresso</strong>для выполнения тест-кейсов с Android UI.</p>
1
<p>Page Object Model (POM) в тестировании - концепция не новая. И если в одних случаях она целесообразна, то в других приводит к дополнительным расходам. В этой статье мы рассмотрим основные механизмы, лежащие в основе POM. Для этого создадим базовую среду тестирования<strong>Espresso</strong>для выполнения тест-кейсов с Android UI.</p>
2
<h2>Основная идея POM</h2>
2
<h2>Основная идея POM</h2>
3
<p>Концепция основана на том, что каждый UI-элемент приложения имеет отдельную реализацию методов. Это позволяет повторно использовать код и легко менять его случае необходимости.</p>
3
<p>Концепция основана на том, что каждый UI-элемент приложения имеет отдельную реализацию методов. Это позволяет повторно использовать код и легко менять его случае необходимости.</p>
4
<p>Кроме того, наличие тестов, написанных согласно этому шаблону, позволит нам создавать легкочитаемый код, который будут хорошо понимать новые члены команды, что сэкономит время.</p>
4
<p>Кроме того, наличие тестов, написанных согласно этому шаблону, позволит нам создавать легкочитаемый код, который будут хорошо понимать новые члены команды, что сэкономит время.</p>
5
<p>В конце концов, при правильном использовании концепции POM автоматизированное тестирование становится доступным для нетехнических членов команды, что тоже немаловажно. Нельзя также не упомянуть и то, что существует большой потенциал при совершенствовании тестовой инфраструктуры.</p>
5
<p>В конце концов, при правильном использовании концепции POM автоматизированное тестирование становится доступным для нетехнических членов команды, что тоже немаловажно. Нельзя также не упомянуть и то, что существует большой потенциал при совершенствовании тестовой инфраструктуры.</p>
6
<h2>Важность концепции POM</h2>
6
<h2>Важность концепции POM</h2>
7
-
<p>Чтобы показать Page Object Model на примере, мы будем использовать реальный проект, созданный в среде Android Studio. Мы выполним рефакторинг уже существующего проекта Espresso, статью о котором можете найти<a>здесь</a>. Этот проект имел 3 страницы: домашнюю страницу (Home page), панель инструментов (Dashboard) и страницу уведомлений (Notifications). Наш тестовый класс нажимал на каждую из этих кнопок и подтверждал, что всё функционирует.</p>
7
+
<p>Чтобы показать Page Object Model на примере, мы будем использовать реальный проект, созданный в среде Android Studio. Мы выполним рефакторинг уже существующего проекта Espresso, статью о котором можете найти<a>здесь</a>. Этот проект имел 3 страницы: домашнюю страницу (Home page), панель инструментов (Dashboard) и страницу у��едомлений (Notifications). Наш тестовый класс нажимал на каждую из этих кнопок и подтверждал, что всё функционирует.</p>
8
<p><em>Простое приложение для Android</em>:</p>
8
<p><em>Простое приложение для Android</em>:</p>
9
<p>А вот как выглядел код нашего приложения:</p>
9
<p>А вот как выглядел код нашего приложения:</p>
10
<p>Как видите, ничего сложного здесь нет. Мы просто вызываем метод click() и подтверждаем, что кнопка отображается.</p>
10
<p>Как видите, ничего сложного здесь нет. Мы просто вызываем метод click() и подтверждаем, что кнопка отображается.</p>
11
<p>В нашем примере только 3 тест-кейса, но представьте, что в реальности у вас 300 тест-кейсов, и вы хотите что-нибудь изменить. Это заставит вас переписать очень много повторяемого кода. Вместо этого лучше воспользоваться шаблоном Page Object Model.</p>
11
<p>В нашем примере только 3 тест-кейса, но представьте, что в реальности у вас 300 тест-кейсов, и вы хотите что-нибудь изменить. Это заставит вас переписать очень много повторяемого кода. Вместо этого лучше воспользоваться шаблоном Page Object Model.</p>
12
<h2>Реализация Page Object Model</h2>
12
<h2>Реализация Page Object Model</h2>
13
<p>Для начала создадим 6 классов, причем каждый класс будет служить определенной цели:</p>
13
<p>Для начала создадим 6 классов, причем каждый класс будет служить определенной цели:</p>
14
<ol><li><strong>EspressoBaseTest</strong>- основной (базовый) класс для хранения методов, которые мы собираемся выполнить. Например, здесь будет находиться метод click().</li>
14
<ol><li><strong>EspressoBaseTest</strong>- основной (базовый) класс для хранения методов, которые мы собираемся выполнить. Например, здесь будет находиться метод click().</li>
15
<li><strong>TestPlan</strong>- основной класс для настройки и выполнения тест-кейсов.</li>
15
<li><strong>TestPlan</strong>- основной класс для настройки и выполнения тест-кейсов.</li>
16
<li><strong>Util</strong>- этот класс обычно используется для выполнения вспомогательных методов. Основная идея заключается в том, чтобы вызывать дополнительные методы, не связанные с нашим EspressoBaseTest, - это улучшит выполнение тестов. Например, здесь будет реализован метод, отвечающий за регистрацию результатов теста. Кроме того, мы могли бы добавить методы для получения скриншотов или видео неудачных тестов с последующей их записью в определённое место в файловой системе.</li>
16
<li><strong>Util</strong>- этот класс обычно используется для выполнения вспомогательных методов. Основная идея заключается в том, чтобы вызывать дополнительные методы, не связанные с нашим EspressoBaseTest, - это улучшит выполнение тестов. Например, здесь будет реализован метод, отвечающий за регистрацию результатов теста. Кроме того, мы могли бы добавить методы для получения скриншотов или видео неудачных тестов с последующей их записью в определённое место в файловой системе.</li>
17
<li><strong>DashboardPage</strong>- методы, специфичные для страницы панели мониторинга. Мы будем хранить идентификаторы ресурсов UI-элементов, используемых на этой странице.</li>
17
<li><strong>DashboardPage</strong>- методы, специфичные для страницы панели мониторинга. Мы будем хранить идентификаторы ресурсов UI-элементов, используемых на этой странице.</li>
18
<li><strong>HomePage</strong>- здесь будем хранить локаторы страниц и получать значения, выполняя определенные методы.</li>
18
<li><strong>HomePage</strong>- здесь будем хранить локаторы страниц и получать значения, выполняя определенные методы.</li>
19
<li><strong>NotificationsPage</strong>- наша третья страница с уведомлениями. Здесь применяется та же логика.</li>
19
<li><strong>NotificationsPage</strong>- наша третья страница с уведомлениями. Здесь применяется та же логика.</li>
20
</ol><p><em>Структура тестового фреймворка</em>:</p>
20
</ol><p><em>Структура тестового фреймворка</em>:</p>
21
<p>Итак, у нас есть 3 метода, которые управляют выполнением теста, и 3 метода, которые представляют UI каждой из страниц приложения. Никакой особой магии здесь нет - мы просто упрощаем процесс и делаем его обслуживаемым в будущем.</p>
21
<p>Итак, у нас есть 3 метода, которые управляют выполнением теста, и 3 метода, которые представляют UI каждой из страниц приложения. Никакой особой магии здесь нет - мы просто упрощаем процесс и делаем его обслуживаемым в будущем.</p>
22
<p>Вот шаги, которые необходимо выполнить, чтобы создать наш проект в Android Studio:</p>
22
<p>Вот шаги, которые необходимо выполнить, чтобы создать наш проект в Android Studio:</p>
23
<ol><li>Создайте новый проект, следуя указаниям<a>этой статьи</a>. Укажите Bottom Navigation Activity в качестве шаблона.</li>
23
<ol><li>Создайте новый проект, следуя указаниям<a>этой статьи</a>. Укажите Bottom Navigation Activity в качестве шаблона.</li>
24
<li>Добавьте следующую зависимость в конец файла Gradle:<em>androidTestImplementation 'com.android.support.test: rules: 1.0.2'</em>.</li>
24
<li>Добавьте следующую зависимость в конец файла Gradle:<em>androidTestImplementation 'com.android.support.test: rules: 1.0.2'</em>.</li>
25
<li>Создайте 6 классов: EspressoBaseTest, TestPlan, Util, DashboardPage, HomePage, NotificationsPage.</li>
25
<li>Создайте 6 классов: EspressoBaseTest, TestPlan, Util, DashboardPage, HomePage, NotificationsPage.</li>
26
</ol><p>После этого нам нужно реализовать<strong>EspressoBaseTest</strong>. Вот как будет выглядеть код:</p>
26
</ol><p>После этого нам нужно реализовать<strong>EspressoBaseTest</strong>. Вот как будет выглядеть код:</p>
27
<p>Как видите, мы берем<strong>идентификатор (ID) ресурса</strong>и нажимаем его. Всё так же, как и раньше, однако теперь эта часть кода имеет возможность повторно использоваться.</p>
27
<p>Как видите, мы берем<strong>идентификатор (ID) ресурса</strong>и нажимаем его. Всё так же, как и раньше, однако теперь эта часть кода имеет возможность повторно использоваться.</p>
28
<p>Следующее, что нужно сделать, - реализовать наши методы для каждой страницы. Эти методы будут хранить наши значения для необходимых идентификаторов ресурсов. Начнём со страницы Dashboard.</p>
28
<p>Следующее, что нужно сделать, - реализовать наши методы для каждой страницы. Эти методы будут хранить наши значения для необходимых идентификаторов ресурсов. Начнём со страницы Dashboard.</p>
29
<p>Поскольку единственный идентификатор, к которому мы обращаемся, - это сама кнопка, напишем метод для возврата этого ресурса:</p>
29
<p>Поскольку единственный идентификатор, к которому мы обращаемся, - это сама кнопка, напишем метод для возврата этого ресурса:</p>
30
<p>Тут всё просто. Единственный вопрос, который возникает - почему мы наследуем<strong>EspressoBaseClass</strong>? Дело в том, что мы хотели бы расширить наш класс, чтобы у нас был доступен click-метод.</p>
30
<p>Тут всё просто. Единственный вопрос, который возникает - почему мы наследуем<strong>EspressoBaseClass</strong>? Дело в том, что мы хотели бы расширить наш класс, чтобы у нас был доступен click-метод.</p>
31
<p>Таким образом, каждый раз, когда мы захотим получить доступ к методу, мы будем использовать метод, доступный в<strong>EspressoBaseTest</strong>. Если же нам нужно будет сделать реализацию для конкретного класса, мы всегда сможем добавить эту логику позже.</p>
31
<p>Таким образом, каждый раз, когда мы захотим получить доступ к методу, мы будем использовать метод, доступный в<strong>EspressoBaseTest</strong>. Если же нам нужно будет сделать реализацию для конкретного класса, мы всегда сможем добавить эту логику позже.</p>
32
<p>Также обратите внимание, что мы объявляем метод getDashboardButton() статическим, чтобы мы могли использовать этот метод без создания экземпляра класса каждый раз.</p>
32
<p>Также обратите внимание, что мы объявляем метод getDashboardButton() статическим, чтобы мы могли использовать этот метод без создания экземпляра класса каждый раз.</p>
33
<p>Осталось повторить ту же логику для двух оставшихся классов:</p>
33
<p>Осталось повторить ту же логику для двух оставшихся классов:</p>
34
<p><strong>HomePage</strong>:</p>
34
<p><strong>HomePage</strong>:</p>
35
<p><strong>NotificationsPage</strong>:</p>
35
<p><strong>NotificationsPage</strong>:</p>
36
<p>Класс Util нужен для создания различных вспомогательных методов, например, логов. Мы будем использовать метод<strong>Log.v</strong>, который уже доступен в Android Framework:</p>
36
<p>Класс Util нужен для создания различных вспомогательных методов, например, логов. Мы будем использовать метод<strong>Log.v</strong>, который уже доступен в Android Framework:</p>
37
<p>Итак, у нас есть почти все части, которые необходимы, кроме одной: реальных тест-кейсов для каждого тестового сценария, который мы хотим выполнить. Однако, мы уже создали класс<strong>TestPlan</strong>, поэтому нам просто нужно написать реализацию для него:</p>
37
<p>Итак, у нас есть почти все части, которые необходимы, кроме одной: реальных тест-кейсов для каждого тестового сценария, который мы хотим выполнить. Однако, мы уже создали класс<strong>TestPlan</strong>, поэтому нам просто нужно написать реализацию для него:</p>
38
<p>Что же изменилось по сравнению с первоначальной реализацией? Разница в следующем: 1) мы скрыли реализацию под методом clickButton(); 2) идентификаторы ресурсов теперь хранятся в каждом соответствующем классе; 3) мы следуем схеме [Page Name].[Method].</p>
38
<p>Что же изменилось по сравнению с первоначальной реализацией? Разница в следующем: 1) мы скрыли реализацию под методом clickButton(); 2) идентификаторы ресурсов теперь хранятся в каждом соответствующем классе; 3) мы следуем схеме [Page Name].[Method].</p>
39
<h2>Запуск тестов</h2>
39
<h2>Запуск тестов</h2>
40
<p>Для запуска щелкните правой кнопкой мыши на класс TestPlan и выберите "Run Test Plan". Поскольку в нашем плане тестирования есть аннотации @Test, Android Studio запустит их как тесты и выведет результаты.</p>
40
<p>Для запуска щелкните правой кнопкой мыши на класс TestPlan и выберите "Run Test Plan". Поскольку в нашем плане тестирования есть аннотации @Test, Android Studio запустит их как тесты и выведет результаты.</p>
41
<p><em>Выполнение тестов в Android Studio</em>:</p>
41
<p><em>Выполнение тестов в Android Studio</em>:</p>
42
<p>Вот и всё, теперь мы задействовали модель POM для нашего проекта. Надеюсь, что эта статья послужит вам примером того, как концепция Page Object Model может помочь при создании UI-тестов для Android.</p>
42
<p>Вот и всё, теперь мы задействовали модель POM для нашего проекта. Надеюсь, что эта статья послужит вам примером того, как концепция Page Object Model может помочь при создании UI-тестов для Android.</p>
43
<p><em><a>Версия статьи на английском</a>.</em></p>
43
<p><em><a>Версия статьи на английском</a>.</em></p>
44
44