сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Автоматизация тестирования

Паттерны автотестов

Как устроен код автотестов

Я разложил 120 тестов двумя способами. В первом селектор кнопки - строка, по которой её находят на странице, - написан прямо в тесте. Во втором он лежит в одном классе страницы, а тесты зовут метод. Потом посчитал, в скольких файлах придётся править селектор после редизайна: в первом случае 120 файлов, во втором - 1. Тестов при этом поровну.

Стержень: локаторы и действия живут в объекте страницы, проверки - в тестах, и правка вёрстки чинится в одном месте.

// Формулировки: «что такое Page Object?», «где должны быть проверки?», «как разделять слои фреймворка?».

Page Object: замер на 120 тестах

Тот замер - вся суть паттерна в одной цифре. Page Object («объект страницы») - это класс, в котором собраны локаторы одной страницы или одного её блока (то есть способы указать программе на конкретный элемент) и действия над ней. Тест перестаёт говорить селекторами и начинает говорить шагами: войти, добавить в корзину, оформить. Читается человеком, который в вёрстке не разбирается вовсе.

Второй эффект - тот самый: 120 файлов против одного. Когда фронтенд-разработчик переименует класс кнопки, в первом варианте это правка ста двадцати тестов и почти гарантированный пропуск пары штук, во втором - одна строка в классе страницы.

// Есть правило, которое делает паттерн рабочим: методы страницы совершают действия и возвращают другую страницу или данные, а проверки остаются в тестах. Если спрятать проверку внутрь объекта страницы, тест превращается в список действий, и по нему уже не понять, что именно он утверждал. Упал такой тест - и надо лезть в код объекта, чтобы выяснить, что вообще проверялось.

class CartPage:
    PAY = '[data-testid=pay]'
    def pay(self):
        self.page.click(self.PAY)
        return CheckoutPage(self.page)

# в тесте: CartPage(page).pay()
# и здесь же - проверки
Page Object
класс с локаторами и действиями одной страницы или блока
правило разделения
действия в объекте страницы, проверки в тесте

Компоненты и слои

Один класс на страницу быстро становится неудобным: шапка, форма поиска, таблица встречаются на десятке страниц. Поэтому объекты делают мельче - на переиспользуемый блок. Класс страницы на 800 строк, куда свалено всё, - тот же неподдерживаемый ком, просто с красивым названием.

Слои разделяют явно. Верхний - тесты: что проверяем. Под ним - шаги, то есть бизнес-действия, собранные из нескольких вызовов страниц («оформить заказ» = корзина плюс адрес плюс оплата). Ниже - объекты страниц и клиенты к серверу: как именно это делается. В самом низу - работа с браузером и сетью. Когда слои смешаны, любая правка расползается по всему проекту.

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

компонентный объект
класс на переиспользуемый блок: шапка, форма, таблица
слой шагов
бизнес-действие, собранное из нескольких вызовов страниц

Данные, имена и мера в обобщении

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

Имена дают по поведению: test_refund_denied_after_30_days вместо test_case_47. Упавший тест читается прямо из отчёта, без открытия кода, - а это половина скорости разбора красного прогона.

// И мера в обобщении. Правило «не повторяйся» (DRY, don't repeat yourself) полезно ровно до момента, когда абстракция становится сложнее дублирования. Хелпер с десятью флагами хуже трёх явных строк, а базовый класс с пятнадцатью пустыми методами «на вырост» - балласт, который никто потом не решится выкинуть. Два повторяющихся вызова терпимее преждевременной абстракции.

фабрика данных
функция, создающая сущность с разумными значениями по умолчанию
имя по поведению
из названия теста понятно, что сломалось, без чтения кода

Как отвечать: «Что такое Page Object и зачем он нужен?»

Page Object - это паттерн, при котором каждая страница или её блок описывается отдельным классом, где собраны локаторы и действия над ними. Польза измеримая. Я раскладывал сто двадцать тестов двумя способами: когда селектор написан прямо в тесте, изменение вёрстки требует правки ста двадцати файлов; когда он лежит в классе страницы - одного. Тестов при этом одинаково. Вторая польза - читаемость: тест говорит шагами - войти, добавить в корзину, оформить, - а не селекторами, и его понимает даже человек, далёкий от вёрстки. Есть важное правило: методы страницы выполняют действия и возвращают следующую страницу или данные, а проверки остаются в тестах. Если спрятать проверку внутрь объекта, упавший тест превращается в загадку - непонятно, что он вообще утверждал. И слежу, чтобы объекты не разрастались: шапку, форму, таблицу выношу в отдельные классы, потому что класс на восемьсот строк - это тот же неподдерживаемый ком, просто с красивым названием.

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

На чём валят

  • Локаторы прямо в тестах: замер показал 120 файлов правки вместо одного.
  • Один класс на всё приложение - конфликты при слиянии и страх что-либо трогать.
  • Проверки внутри методов страницы - упавший тест не говорит, что проверял.
  • Копипаста подготовки данных по тестам: сменилась модель - чинить всюду.
  • Абстракция на будущее: базовый класс с пятнадцатью пустыми методами «на вырост».

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.

  1. #design_patterns1 / 5
    Что держать в Page Object, а что — в самом тесте?
    A)В Page Object держат сами проверки (ассерты), а в тесте — локаторы элементов
    B)В Page Object хранят тестовые данные, а в тесте — методы взаимодействия со страницей
    C)В Page Object — локаторы и действия; в тесте — сценарий и проверки бизнес-смысла
    D)И локаторы, и сценарий, и проверки держат в одном классе теста ради простоты
    показать ответ и разбор
    +C)В Page Object — локаторы и действия; в тесте — сценарий и проверки бизнес-смысла

    // разбор: Разделение ответственности — суть паттерна. Page Object знает, КАК взаимодействовать со страницей: где элементы (локаторы) и как выполнить действие (кликнуть, ввести, отправить форму) — методы вроде fillLogin(), submit(). Тест знает, ЧТО проверяется: он выстраивает сценарий из этих действий (открыть логин → ввести → отправить) и делает проверки бизнес-смысла (после логина показана панель, баланс верен). Ассерты обычно живут в тесте, а не в Page Object, чтобы страница оставалась переиспользуемым «пультом», а проверки — гибкими под каждый сценарий. Так тест читаем, а детали UI изолированы.

  2. #design_patterns2 / 5
    Почему локаторы не хардкодят прямо в каждом тесте?
    A)Потому что хардкод локатора замедляет поиск элемента на странице во время прогона
    B)Потому что тест с хардкодом локатора не запустится в headless-режиме браузера
    C)Потому что локаторы, вписанные в тест, видны в отчёте и раскрывают структуру страницы
    D)Дублированный по тестам селектор при перевёрстке приходится править везде
    показать ответ и разбор
    +D)Дублированный по тестам селектор при перевёрстке приходится править везде

    // разбор: Если селектор кнопки скопирован в двадцать тестов напрямую, то одно изменение вёрстки требует найти и поправить его во всех двадцати — вручную, с риском что-то пропустить и оставить часть набора красной. Это классическое нарушение DRY: одно знание («как найти эту кнопку») размазано по многим местам. Централизация локаторов в Page Object (или в отдельном реестре) делает правку одноточечной: изменили — и все тесты подхватили. Хардкод по тестам превращает поддержку в дорогую и хрупкую операцию, а по мере роста набора эта цена растёт лавинообразно, отчего команды и забрасывают такие наборы.

  3. #design_patterns3 / 5
    Как принцип DRY проявляется в автотестах?
    A)Повторяющиеся шаги (логин, подготовка данных) выносят в хелперы и фикстуры
    B)DRY требует писать как можно больше тестов, чтобы покрыть каждый сценарий отдельно
    C)DRY означает запускать каждый тест дважды для подтверждения воспроизводимости результата
    D)DRY предписывает держать все шаги теста в одном длинном методе без вспомогательных функций
    показать ответ и разбор
    +A)Повторяющиеся шаги (логин, подготовка данных) выносят в хелперы и фикстуры

    // разбор: DRY (Don't Repeat Yourself) — «одно знание в одном месте». В тестах повторяется многое: логин перед сценарием, подготовка тестовых данных, типовые проверки, обёртки над действиями. Если копировать эти блоки из теста в тест, любое изменение (сменился флоу логина) придётся вносить во все копии — долго и с ошибками. Вместо этого общие шаги выносят в переиспользуемые хелперы, фикстуры (setup), базовые классы, Page Object. Тогда правка делается один раз и подхватывается всеми. DRY снижает объём кода и, главное, стоимость его поддержки — ключевого фактора живучести автонабора.

  4. #design_patterns4 / 5
    Зачем отделять тестовые данные от логики теста?
    A)Чтобы тесты выполнялись быстрее, ведь данные из отдельного файла грузятся оперативнее
    B)Входы и ожидания выносят в параметры или фикстуры
    C)Чтобы скрыть тестовые данные от разработчиков, работающих с тем же репозиторием
    D)Чтобы каждый набор данных проверялся отдельным, независимо написанным тестом
    показать ответ и разбор
    +B)Входы и ожидания выносят в параметры или фикстуры

    // разбор: Когда конкретные значения (логины, суммы, ожидаемые результаты) вшиты в тело теста, каждый новый набор данных требует копии всего теста. Отделив данные (в параметры, таблицы, фикстуры, внешние файлы), один и тот же сценарий прогоняют на множестве входов — data-driven-подход: проверить логин на десятке валидных/невалидных пар, не дублируя код. Плюс: данные видно и легко менять/расширять, а логика сценария остаётся одна. Это и уменьшает дублирование, и повышает покрытие. Тестировщик добавляет строку данных, а не пишет новый тест.

  5. #design_patterns5 / 5
    Каким должен быть хорошо написанный автотест по читаемости?
    A)Тест должен содержать как можно больше низкоуровневых команд драйвера для полноты контроля
    B)Тест лучше писать одним длинным методом без комментариев, чтобы он занимал меньше строк
    C)Читается как сценарий из бизнес-шагов (openLogin, submit, expect balance), а не из низкоуровневых кликов и селекторов
    D)Читаемость теста не важна: главное, чтобы он проходил, а понимать его будет автор
    показать ответ и разбор
    +C)Читается как сценарий из бизнес-шагов (openLogin, submit, expect balance), а не из низкоуровневых кликов и селекторов

    // разбор: Читаемый тест выражает намерение на языке предметной области: openLoginPage(), login(user), expectDashboardVisible() — по нему сразу понятно, что проверяется, даже без знания селекторов. Если же тело теста — россыпь driver.findElement(By.xpath(...)).click() и ручных ожиданий, смысл сценария тонет в технических деталях, тест труден для чтения и правки. Достигают читаемости выносом деталей в Page Object и хелперы, осмысленными именами методов и структурой «arrange-act-assert». Читаемый тест дешевле поддерживать и он сам служит документацией поведения.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.