Добрый день, меня зовут Виктория и я много лет занимаюсь автоматизацией. В этой статье я хотела бы рассказать о паттернах автоматизации, которые использую, а также о такой штуке, как архитектура проекта.
Я разворачивала проекты на разных языках программирования и для различных типов проектов (мобильные, веб, как чистый фронтенд, так и бэкенд). Для себя я выделила 6 паттернов программирования:
-
Page Object Pattern (Паттерн Объекта Страницы)
Этот паттерн разделяет логику взаимодействия с веб-страницами от тестового кода, улучшая читаемость и обслуживаемость тестов. Паттерн позволяет отделить логику тестирования от логики реализации веб-страниц.
-
Data Generation Pattern (Паттерн генерации данных)
К этому паттерну относится:
2.1. Классический Data-Driven Testing (Тестирование на Основе Данных) паттерн, который позволяет запускать один и тот же тест с разными наборами данных для проверки различных сценариев.
2.2. Я также отношу к нему создание отдельных классов или библиотек, которые автоматически генерирует тестовые данные для тестов (рандомные и/или кастомные).
-
Steps Pattern
Этот паттерн отвечает за создания слоя бизнес-логики. Это паттерн проектирования автоматизированных тестов или автоматизации действий, когда тестовый сценарий разбивается на отдельные шаги (steps) или этапы для удобства выполнения и поддержки. Чаще всего каждый шаг описывает какое-то бизнес действие системы. Такое как: "Пользователь_вошел_в_систему", "Пользователь_сделал_заказ" и прочие.
Этот подход позволяет разделять тестовые действия на небольшие и понятные шаги, каждый из которых выполняет конкретное действие или проверку. Каждый шаг теста представляет собой отдельную функцию, метод или действие, которое может быть повторно использовано в различных тестовых сценариях.
В этот паттерн я включаю:
3.1. Классический Behavior-Driven Development (BDD) Pattern - о нем можно почитать самостоятельно.
3.2. Любой другой код суть которого описать поведение пользователя в отдельных блоках / шагах / методами / ключевых словах.
-
Паттерн проверок (Assert/Checker)
Это паттерн в тестировании программного обеспечения, который используется для утверждения (assert) того, что программа работает ожидаемым образом и находится в определенном состоянии после выполнения определенных действий. Основная цель этого паттерна - проверка корректности функционирования программы на разных этапах ее выполнения. Это включает в себя утверждение фактов о состоянии данных, результатах выполнения кода или ожидаемых действиях программы.
Примеры этого паттерна: сравнения ожидаемых результатов с фактическими, проверка, что программа находится в определенном состоянии после выполнения определенных операций или функций. Паттерн также переиспользуеться в разных тестах для разных проверок.
-
Паттерн логирования
Паттерн логирования - это методика, используемая для записи информации о действиях, событиях и состоянии системы во время ее работы. Целью логирования является создание удобочитаемых и информативных записей, которые могут помочь при отладке, мониторинге и анализе работы программы или системы.
Эти паттерны помогают создавать структурированные и устойчивые тесты, повышая их читаемость, обслуживаемость и эффективность.
Но возникает вопрос: как же эти паттерны работают вместе и образую одну единую архитектуру?
Для себя я выделила следующую структуру- есть три слоя или контекстных области, которые взаимодействуют друг с другом:
Контекстная область взаимодействия с приложением, которое мы тестируем. Я называю ее ядром (Core).
Контекстная область тестов (Tests).
Контекстная область раннера автотестов (Runner).
Первый слой - это ядро, в котором находятся материалы, связанные с описанием нашего приложения и взаимодействием нашего кода с ним. Он содержит методы, которые вызывают API, методы. Они в свою очередь взаимодействуют с базой данных, методы, которые описывают архитектуру веб страниц с локаторами. Тут описывается все, что связано с контекстом приложения. Сюда относится паттерн Page Object Pattern.
Второй слой - тестовый слой. Сюда относится всё, что связано с автотестами, включая всё необходимое для запуска тестов (пред настройки), создания тестовых данных для тестов и проверок, которые происходят в тесте. Я называю этот слой - контекстом автотестов. Сюда относятся паттерн шагов, паттерн проверок и паттерн данных.
Третий слой - это слой раннера автотестов. Сюда относится паттерн логирования и всё, что связано с запуском тестов в определенной среде (предварительные настройки, последующие настройки).
Расскажу чуть подробнее про каждый слой:
1. Контекстная область взаимодействия с приложением
Core - в нем находятся материалы, связанные с описанием нашего приложения и взаимодействием нашего кода с ним. Он содержит три блока. Эти блоки представляют собой модули, которые работают независимо друг от друга, обеспечивая модульность, читаемость и легкость поддержки:
1.Блок Page Object - это шаблон проектирования для автоматизации тестирования веб-приложений. Он предполагает создание объектно-ориентированных моделей страниц вашего веб-приложения, которые отражают функциональность и элементы интерфейса страниц.
Page Object упрощает обслуживание тестовых сценариев, так как при изменении элементов интерфейса или логики страницы, нужно вносить изменения только в соответствующий объект Page Object, не затрагивая другие части тестового кода.
Кратко говоря, Page Object позволяет абстрагировать веб-страницы в объекты, делая код тестов более читаемым, модульным и легким в поддержке.
2.Блок API методы - этот блок предоставляет функции для взаимодействия с веб-сервисом API. Он включает отправку запросов к бекенд серверу и перехват запросов, что может быть полезным для эмуляции данных или обработки входящих запросов.
Отправка запросов: позволяют через API получить данные или взаимодействовать с данными на сервере.
Перехват запросов: некоторые API методы также могут предоставлять возможность перехвата или обработки запросов, которые поступают к приложению. Это может быть полезным для моканья ответа с сервера.
3.Блок BD методы - предоставляет собой инструмент взаимодействия в базой данных из кода.
Основные аспекты этих блоков включают:
-
Модульность и виртуальные границы: Каждый блок представляет собой отдельный компонент с четко определенными функциями. Они не зависят друг от друга и выполняют свою задачу независимо. К примеру, если вам нужен метод, который проверит, что вся страница "Заказы" загрузилась корректно т.е. метод. который визуально просмотрит страницу на предмет отображения элементов и проверит, что API запрос пришел со статусом 200, тогда вам нужно для этого создать тестовый шаг.
Этот шаг будет описан в слое Tests в блоке Steps. Его можно назвать User_check_opders_page. Он будет включать в себя переход на страницу через pageOpject order класс этой страницы, проверка видимости элементов страницы, а также в в этом шаге будет проверка на то, что API запрос о получение данных всех заказов вернулся со статусом 200.
Использование настроек: Настройки для вызова методов, такие как конфигурации базы данных, доменные URL страниц для Page Object, и URL для API, хранятся в слое runner. Это упрощает изменение конфигурации без изменения кода.
2. Контекстная область тестовых сценариев
Концепция второго слоя, тестового слоя, включает в себя все аспекты автотестов. Она включает в себя несколько ключевых компонентов:
Файлы автотестов: это сценарии, тест- кейсы или файлы, в которых описаны автотесты.
Блок генерации тестовых данных: эти методы используются для создания данных, необходимых для проведения тестов. Обычно эти методы вызываются в начале теста для подготовки данных прогона тестов.
Тестовые шаги (Steps): они представляют собой методы, которые описывают последовательность действий, выполняемых пользователем в приложении. Эти шаги выполняются в тесте. Эти методы как раз таки используют методы из первого слоя (например, методы Page Object) для взаимодействия с тестируемой системой.
Проверки состояния системы (Check): после выполнения тестовых шагов идут проверки для подтверждения, что система находится в ожидаемом состоянии. Эти проверки сравнивают фактическое состояние системы с ожидаемым результатом. Они также проверяют состояние системы на соответствие ожиданиям.
На диаграмме ниже показаны то, как блоки вызывают друг друга.
Реальный тест включает в себя следующий ход:
В начале автотеста вызывается Шаг, который вызывает генерацию тестовых данных. Эти данные в дальнейшем используются в тесте. Потом выполняются шаги пользователя в системы. В конце автотеста вызывается шаг, который вызывает методы проверки состояния системы.
Получаем следующий тестовый путь:
Подготовка тестовых данных
Выполнение шагов взаимодействия с системой
Проверка состояния системы
Вынос генерации тестовых данных и тестовых проверок в отдельные методы — это не только организационная стратегия, но и мощное средство для повторного использования, в процессе создания тестовых сценариев. Такой подход дарит нам возможность эффективно использовать эти методы в различных тестах, создавая универсальные инструменты, которые не привязаны к конкретному сценарию.
Вынося генерацию тестовых данных в отдельные методы, мы создаем механизм, который может создавать разнообразные варианты начального состояния системы для различных тест-кейсов.
Точно так же, разбивая последовательные действия пользователя на отдельные шаги, мы формируем модули, которые описывают конкретные этапы взаимодействия с системой. Эти шаги становятся повторно используемыми блоками, что облегчает поддержку и модификацию тестовых сценариев. Кроме того, такая модульная структура улучшает читаемость кода и позволяет легко добавлять или изменять шаги без внесения изменений во всю тестовую логику.
3. Контекстная область запуска автотестов
Кратко расскажу о содержании этого слоя. Он включает в себя настройку окружения, логирование и создание скриншотов во время выполнения автотестов, создание репортов и отправку репортов в различные каналы. Кроме того, сюда входят глобальные хуки "before" и "after", где осуществляются глобальные настройки, передача переменных окружения, предварительная и завершающая настройка приложения. Важно отметить, что эти блоки также независимы друг от друга и связаны (вызываются) исключительно с тестовым слоем. То есть, имеют свои виртуальные границы. Отдельно настройки окружения, отдельно логирование, отдельно создание репорта и отправка.
4. Как между собой взаимодействуют слои
Слои взаимодействия в этой архитектуре крайне четко определены. Методы из ядра напрямую общаются только с шагами. Слой Core, содержащий основные методы и функциональность, организован таким образом, чтобы взаимодействовать исключительно с шагами, облегчая тем самым структуру и чистоту кода.
С другой стороны, компоненты раннера (или компоненты запуска) взаимодействуют только с сущностью автотестов, или, иначе говоря, с основными элементами, необходимыми для выполнения тестовых сценариев. Этот слой фокусируется на подготовке и запуске самих тестов, обеспечивая эффективное и правильное их выполнение.
5. Общая картина нашей структуры и взаимодействия
Общая картина нашей архитектуры выглядит как на картинке нижу, однако, мы все понимаем, что в реальности такая структурна не всегда нужна.
Важно понимать, что созданный нами фреймворк представляет собой все таки монолит - набор модулей, которые мы никогда не будем выносить отдельно в микросервисы. Однако, необходимо учитывать логические связи и предотвращать их пересечение. В нашем контексте важно соблюдать эти виртуальные границы, чтобы поддерживать структурную чистоту и управляемость нашего фреймворка.
Подводя итог всего написанного выше: выносите контексты в отдельные модули и соблюдайте виртуальные границы )))
Спасибо за внимание!
Комментарии (6)
TheRealSova
19.12.2023 20:24Хорошая статья, все достаточно понятно описано, применяю аналогичные паттерны в своей работе!
Вопрос к автору: представьте, что изначально у вас проект для тестирования API, потом вы решили добавить туда ещё и UI тесты, в которых для подготовки данных вы хотите переиспользовать готовые API Steps, будете ли вы наследоваться от первого тестового проекта или будете реализовать похожие Steps, но уже в проекте с UI тестами?
velizaryan Автор
19.12.2023 20:24Привет. Шаг от шага все таки наследовать плохая практика (мое ИМХО). Мой подход в этом плане такой: я выношу какие то действия в шаге в отдельные функции и вот их уже вызывать в разных шагах. На мой взгляд также можно в UI тестах использовать готовые API шаги без наследования. Условно говоря есть UI шаг "я добавил заказ". Потом идет шаг проверить API ответ для этого я просто использую API шаг: "я проверил что наш заказ есть в полученном API запросе заказов".
Вообще в практике если позволяет приложения я придерживаюсь концепции для UI тестов: "один тест одна страница" . то есть если мой UI тест проверяет создание заказа то он визуально проверяет только этот экран - остальное проверяется через API. Тоже самое если я проверяю страницу списка заказов я пред настройку делаю через API и проверяю только что визуально я вижу те заказы что до этого создал мой тест через API )
m_aleksei
19.12.2023 20:24В общем вы все правильно описали!
Но в идеале хорошо приводить примеры кода тех или иных частей. И как это взаимодействут друг с другом.
ЗЫ вот у нас в поекте есть все части что вы упомянули. Вообще все. Мы делали UI тесты, а все остальное приросло по ходу и API и DB. Как итог API + DB вынесли в отдельный модуль и теперь это используется в 5ти других проектах.
Exited
19.12.2023 20:24Странно, для новичков слишком много абстракции, нет примеров паттернов, например checker, steps, а опытные и так это знали, вообщем я немного в замешательстве
onets
Довольно монструозно выглядит. А это действительно все надо? Хотелось бы чтобы тесты были простыми, но эффективными.
И если речь заходит об архитектуре - то статья начинается где-то с середины. Я ожидал, что вначале будет про тестирование черного и белого ящика. Как пример могу привести - тестирование api через rest вызовы и тестирование того же api с прямым вызовом бизнес слоя.
Далее собственно про сами слои (есть ui, бизнес логика, и хранилище данных, могут быть внешние системы). Для каждого слоя характерны те или иные паттерны.
Далее интересно было бы узнать - что надо тестировать, а что нет. Например нет смысла тестировать БД как таковую. Она уже оттестирована разработчиками БД. Есть смысл тестировать БД в связке с бизнес логикой.
Следующим пунктом интересно было бы увидеть систематизированную информацию о том, как обеспечить повторяемость тестов. Дата генерация конечно хорошо, но перед повторным запуском тестов - надо все это откатить.
И далее уже расписывать паттерны детальней.
И самая интересная часть для меня - это тестирование взаимодействия с внешними системами. Эта часть ломается чаще всего по моему опыту. Но вместе с тем там крайне хрупкие тесты.
velizaryan Автор
Этого всего конечно же не надо. Это пример абстрактной архитектуры в вакууме. На практике в разных проектах все может быть вообще по другому. Нужно брать только то что действительно нужно не забывая про принцип простоты. Нужно брать только то что действительно нужно твоему проекту.