HTML Diff
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