Добрый день, меня зовут Виктория и я много лет занимаюсь автоматизацией. В этой статье я хотела бы рассказать о паттернах автоматизации, которые использую, а также о такой штуке, как архитектура проекта.
Я разворачивала проекты на разных языках программирования и для различных типов проектов (мобильные, веб, как чистый фронтенд, так и бэкенд). Для себя я выделила 6 паттернов программирования:

  1. Page Object Pattern (Паттерн Объекта Страницы)

    Этот паттерн разделяет логику взаимодействия с веб-страницами от тестового кода, улучшая читаемость и обслуживаемость тестов. Паттерн позволяет отделить логику тестирования от логики реализации веб-страниц.

  2. Data Generation Pattern (Паттерн генерации данных)

    К этому паттерну относится:

    2.1. Классический Data-Driven Testing (Тестирование на Основе Данных) паттерн, который позволяет запускать один и тот же тест с разными наборами данных для проверки различных сценариев.

    2.2. Я также отношу к нему создание отдельных классов или библиотек, которые автоматически генерирует тестовые данные для тестов (рандомные и/или кастомные).

  3. Steps Pattern

    Этот паттерн отвечает за создания слоя бизнес-логики. Это паттерн проектирования автоматизированных тестов или автоматизации действий, когда тестовый сценарий разбивается на отдельные шаги (steps) или этапы для удобства выполнения и поддержки. Чаще всего каждый шаг описывает какое-то бизнес действие системы. Такое как: "Пользователь_вошел_в_систему", "Пользователь_сделал_заказ" и прочие.

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

    В этот паттерн я включаю:

    3.1. Классический Behavior-Driven Development (BDD) Pattern - о нем можно почитать самостоятельно.

    3.2. Любой другой код суть которого описать поведение пользователя в отдельных блоках / шагах / методами / ключевых словах.

  4. Паттерн проверок (Assert/Checker)

    Это паттерн в тестировании программного обеспечения, который используется для утверждения (assert) того, что программа работает ожидаемым образом и находится в определенном состоянии после выполнения определенных действий. Основная цель этого паттерна - проверка корректности функционирования программы на разных этапах ее выполнения. Это включает в себя утверждение фактов о состоянии данных, результатах выполнения кода или ожидаемых действиях программы.

    Примеры этого паттерна: сравнения ожидаемых результатов с фактическими, проверка, что программа находится в определенном состоянии после выполнения определенных операций или функций. Паттерн также переиспользуеться в разных тестах для разных проверок.

  5. Паттерн логирования

    Паттерн логирования - это методика, используемая для записи информации о действиях, событиях и состоянии системы во время ее работы. Целью логирования является создание удобочитаемых и информативных записей, которые могут помочь при отладке, мониторинге и анализе работы программы или системы.

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

Но возникает вопрос: как же эти паттерны работают вместе и образую одну единую архитектуру?

Для себя я выделила следующую структуру- есть три слоя или контекстных области, которые взаимодействуют друг с другом:

  • Контекстная область взаимодействия с приложением, которое мы тестируем. Я называю ее ядром (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 методы - предоставляет собой инструмент взаимодействия в базой данных из кода.

Основные аспекты этих блоков включают:

  1. Модульность и виртуальные границы: Каждый блок представляет собой отдельный компонент с четко определенными функциями. Они не зависят друг от друга и выполняют свою задачу независимо. К примеру, если вам нужен метод, который проверит, что вся страница "Заказы" загрузилась корректно т.е. метод. который визуально просмотрит страницу на предмет отображения элементов и проверит, что API запрос пришел со статусом 200, тогда вам нужно для этого создать тестовый шаг.

    Этот шаг будет описан в слое Tests в блоке Steps. Его можно назвать User_check_opders_page. Он будет включать в себя переход на страницу через pageOpject order класс этой страницы, проверка видимости элементов страницы, а также в в этом шаге будет проверка на то, что API запрос о получение данных всех заказов вернулся со статусом 200.

  2. Использование настроек: Настройки для вызова методов, такие как конфигурации базы данных, доменные URL страниц для Page Object, и URL для API, хранятся в слое runner. Это упрощает изменение конфигурации без изменения кода.

2. Контекстная область тестовых сценариев

Концепция второго слоя, тестового слоя, включает в себя все аспекты автотестов. Она включает в себя несколько ключевых компонентов:

  1. Файлы автотестов: это сценарии, тест- кейсы или файлы, в которых описаны автотесты.

  2. Блок генерации тестовых данных: эти методы используются для создания данных, необходимых для проведения тестов. Обычно эти методы вызываются в начале теста для подготовки данных прогона тестов.

  3. Тестовые шаги (Steps): они представляют собой методы, которые описывают последовательность действий, выполняемых пользователем в приложении. Эти шаги выполняются в тесте. Эти методы как раз таки используют методы из первого слоя (например, методы Page Object) для взаимодействия с тестируемой системой.

  4. Проверки состояния системы (Check): после выполнения тестовых шагов идут проверки для подтверждения, что система находится в ожидаемом состоянии. Эти проверки сравнивают фактическое состояние системы с ожидаемым результатом. Они также проверяют состояние системы на соответствие ожиданиям.

На диаграмме ниже показаны то, как блоки вызывают друг друга.

Реальный тест включает в себя следующий ход:

В начале автотеста вызывается Шаг, который вызывает генерацию тестовых данных. Эти данные в дальнейшем используются в тесте. Потом выполняются шаги пользователя в системы. В конце автотеста вызывается шаг, который вызывает методы проверки состояния системы.

Получаем следующий тестовый путь:

  • Подготовка тестовых данных

  • Выполнение шагов взаимодействия с системой

  • Проверка состояния системы

Вынос генерации тестовых данных и тестовых проверок в отдельные методы — это не только организационная стратегия, но и мощное средство для повторного использования, в процессе создания тестовых сценариев. Такой подход дарит нам возможность эффективно использовать эти методы в различных тестах, создавая универсальные инструменты, которые не привязаны к конкретному сценарию.

Вынося генерацию тестовых данных в отдельные методы, мы создаем механизм, который может создавать разнообразные варианты начального состояния системы для различных тест-кейсов.

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

3. Контекстная область запуска автотестов

Кратко расскажу о содержании этого слоя. Он включает в себя настройку окружения, логирование и создание скриншотов во время выполнения автотестов, создание репортов и отправку репортов в различные каналы. Кроме того, сюда входят глобальные хуки "before" и "after", где осуществляются глобальные настройки, передача переменных окружения, предварительная и завершающая настройка приложения. Важно отметить, что эти блоки также независимы друг от друга и связаны (вызываются) исключительно с тестовым слоем. То есть, имеют свои виртуальные границы. Отдельно настройки окружения, отдельно логирование, отдельно создание репорта и отправка.

4. Как между собой взаимодействуют слои


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

С другой стороны, компоненты раннера (или компоненты запуска) взаимодействуют только с сущностью автотестов, или, иначе говоря, с основными элементами, необходимыми для выполнения тестовых сценариев. Этот слой фокусируется на подготовке и запуске самих тестов, обеспечивая эффективное и правильное их выполнение.

5. Общая картина нашей структуры и взаимодействия

Общая картина нашей архитектуры выглядит как на картинке нижу, однако, мы все понимаем, что в реальности такая структурна не всегда нужна.

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

Подводя итог всего написанного выше: выносите контексты в отдельные модули и соблюдайте виртуальные границы )))

Спасибо за внимание!

Комментарии (6)


  1. onets
    19.12.2023 20:24

    Довольно монструозно выглядит. А это действительно все надо? Хотелось бы чтобы тесты были простыми, но эффективными.

    И если речь заходит об архитектуре - то статья начинается где-то с середины. Я ожидал, что вначале будет про тестирование черного и белого ящика. Как пример могу привести - тестирование api через rest вызовы и тестирование того же api с прямым вызовом бизнес слоя.

    Далее собственно про сами слои (есть ui, бизнес логика, и хранилище данных, могут быть внешние системы). Для каждого слоя характерны те или иные паттерны.

    Далее интересно было бы узнать - что надо тестировать, а что нет. Например нет смысла тестировать БД как таковую. Она уже оттестирована разработчиками БД. Есть смысл тестировать БД в связке с бизнес логикой.

    Следующим пунктом интересно было бы увидеть систематизированную информацию о том, как обеспечить повторяемость тестов. Дата генерация конечно хорошо, но перед повторным запуском тестов - надо все это откатить.

    И далее уже расписывать паттерны детальней.

    И самая интересная часть для меня - это тестирование взаимодействия с внешними системами. Эта часть ломается чаще всего по моему опыту. Но вместе с тем там крайне хрупкие тесты.


    1. velizaryan Автор
      19.12.2023 20:24

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


  1. TheRealSova
    19.12.2023 20:24

    Хорошая статья, все достаточно понятно описано, применяю аналогичные паттерны в своей работе!

    Вопрос к автору: представьте, что изначально у вас проект для тестирования API, потом вы решили добавить туда ещё и UI тесты, в которых для подготовки данных вы хотите переиспользовать готовые API Steps, будете ли вы наследоваться от первого тестового проекта или будете реализовать похожие Steps, но уже в проекте с UI тестами?


    1. velizaryan Автор
      19.12.2023 20:24

      Привет. Шаг от шага все таки наследовать плохая практика (мое ИМХО). Мой подход в этом плане такой: я выношу какие то действия в шаге в отдельные функции и вот их уже вызывать в разных шагах. На мой взгляд также можно в UI тестах использовать готовые API шаги без наследования. Условно говоря есть UI шаг "я добавил заказ". Потом идет шаг проверить API ответ для этого я просто использую API шаг: "я проверил что наш заказ есть в полученном API запросе заказов".

      Вообще в практике если позволяет приложения я придерживаюсь концепции для UI тестов: "один тест одна страница" . то есть если мой UI тест проверяет создание заказа то он визуально проверяет только этот экран - остальное проверяется через API. Тоже самое если я проверяю страницу списка заказов я пред настройку делаю через API и проверяю только что визуально я вижу те заказы что до этого создал мой тест через API )


  1. m_aleksei
    19.12.2023 20:24

    В общем вы все правильно описали!

    Но в идеале хорошо приводить примеры кода тех или иных частей. И как это взаимодействут друг с другом.

    ЗЫ вот у нас в поекте есть все части что вы упомянули. Вообще все. Мы делали UI тесты, а все остальное приросло по ходу и API и DB. Как итог API + DB вынесли в отдельный модуль и теперь это используется в 5ти других проектах.


  1. Exited
    19.12.2023 20:24

    Странно, для новичков слишком много абстракции, нет примеров паттернов, например checker, steps, а опытные и так это знали, вообщем я немного в замешательстве