Статьи

Ruby Page Объекты для ценителей капибары

Конечный продукт
Что вы будете создавать

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

Этот метод позволяет вам написать высокоуровневые характеристики, которые очень выразительны и СУХО. В некотором смысле, это приемочные тесты с языком приложения. Вы можете спросить, разве спецификации, написанные на Capybara, не являются высокоуровневыми и выразительными? Конечно, для разработчиков, которые пишут код ежедневно, спецификации Capybara читаются просто отлично. Они сухие из коробки? Не совсем — на самом деле, конечно, нет!

Функция « ruby ​​’M создает миссию’ делает сценарий ‘успешно’ делает sign_in_as ‘M@mi6.com’

« `функция ruby ​​’M помечает миссию как завершенную’ do сценарий ‘успешно’ do sign_in_as ‘M@mi6.com’

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

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

Инкапсуляция является ключевым понятием с объектами Page. Когда вы пишете спецификации своих функций, вы извлекаете выгоду из стратегии, позволяющей извлечь поведение, которое влияет на процесс тестирования. Что касается качественного кода, вы хотите фиксировать взаимодействия с определенными наборами элементов на своих страницах, особенно если вы наткнулись на повторяющиеся шаблоны. По мере роста вашего приложения вам нужен / нужен подход, позволяющий избежать распространения этой логики на все ваши спецификации.

«Ну, разве это не излишество? Капибара отлично читает!

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

Из-за ежедневных изменений ваш код Capybara уязвим при распространении повсеместно — он вводит возможные точки останова. Допустим, дизайнер хочет изменить текст на кнопке. Не важно, верно? Но вы хотите приспособиться к этому изменению в одной центральной оболочке для этого элемента в ваших спецификациях, или вы предпочитаете делать это повсеместно? Я так думала!

Существует множество возможных рефакторингов для ваших спецификаций компонентов, но Page Objects предлагает самые чистые абстракции для инкапсуляции поведения пользователя для страниц или более сложных потоков. Вам не нужно имитировать всю страницу (ы), хотя сосредоточиться на основных битах, которые необходимы для пользовательских потоков. Не нужно переусердствовать!

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

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

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

Такие инструменты, как Capybara, помогут вам избежать этого вручную, а это означает, что вам редко приходится открывать браузер, чтобы протестировать данные вручную. С помощью такого рода тестов мы хотели бы максимально автоматизировать эти задачи и тестировать взаимодействие через браузер при написании утверждений для страниц. Кстати, вы не используете get , put , post или delete как вы используете спецификации запросов.

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

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

Я надеюсь, что прояснение этой расплывчатой ​​терминологии поможет вам увидеть, что наличие Page Objects немного похоже на тестирование на приемочном уровне при написании спецификаций функций.

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

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

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

Давайте рассмотрим два простых примера спецификаций функций, которые позволяют M создавать секретные миссии, которые затем можно выполнить.

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

спецификации / особенности / m_creates_a_mission_spec.rb

« `ruby require ‘rails_helper’

функция «M создает миссию» делает сценарий «успешно» делает sign_in_as «M@mi6.com»

def create_classified_mission_named (mission_name) посещение missions_path click_on «Создать миссию», заполните «Имя миссии», с помощью: mission_name click_button «Submit» end

def agent_sees_mission (mission_name) ожидаемый (страница) .to have_css ‘li.mission-name’, текст: название_миссии end

def sign_in_as (электронная почта) посетите root_path fill_in ‘Email’, с: email click_button ‘Submit’ end end « `

спецификации / особенности / agent_completes_a_mission_spec.rb

« `ruby require ‘rails_helper’

функция «M помечает миссию как выполненную», успешно выполнить сценарий, выполнить sign_in_as «M@mi6.com»

def create_classified_mission_named (mission_name) посещение missions_path click_on «Создать миссию», заполните «Имя миссии», с помощью: mission_name click_button «Submit» end

def agent_sees_completed_mission (mission_name) ожидаемый (страница) .to have_css ‘ul.missions li.mission-name.completed’, текст: название_миссии end

def sign_in_as (электронная почта) посетите root_path fill_in ‘Email’, с: email click_button ‘Submit’ end end « `

Хотя, конечно, есть и другие способы работы с такими вещами, как sign_in_as , create_classified_mission_named и т. Д., Легко увидеть, как быстро эти вещи могут начать сосать и накапливаться.

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

Давайте сделаем небольшой магический трюк, где я пока скрываю реализацию объекта Page Object и показываю только конечный результат, примененный к спецификациям функции выше:

спецификации / особенности / m_creates_a_mission_spec.rb

« `ruby require ‘rails_helper’

функция «M создает миссию», выполнить сценарий «успешно», выполнить sign_in_as «M@mi6.com». Посетить missions_path mission_page = Pages :: Missions.new

спецификации / особенности / agent_completes_a_mission_spec.rb

« `ruby require ‘rails_helper’

функция «M помечает миссию как завершенную», «выполнить сценарий» успешно, «выполнить sign_in_as», M@mi6.com, посетить missions_path mission_page = Pages :: Missions.new

Не слишком плохо читает, а? Вы в основном создаете выразительные методы-обертки на своих объектах Page, которые позволяют вам иметь дело с высокоуровневыми концепциями — вместо того, чтобы постоянно возиться с кишками вашей разметки. Ваши извлеченные методы делают сейчас эту грязную работу, и таким образом операция с дробовиком больше не является вашей проблемой.

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

функции / поддержка / функция / страницы / missions.rb

« `ruby module Страницы класса Missions включают Capybara :: DSL

То, что вы видите, представляет собой обычный старый объект Ruby. Объекты Page по сути являются очень простыми классами. Обычно вы не создаете экземпляры Page Objects с данными (когда, конечно, вы можете это сделать), и вы в основном создаете язык через API, который может использовать пользователь или нетехнический участник команды. Когда вы думаете о наименовании ваших методов, я думаю, что это хороший совет, чтобы задать себе вопрос: как бы пользователь описал поток или предпринятые действия?

Возможно, мне следует добавить, что без включения Capybara музыка останавливается довольно быстро.

ruby include Capybara::DSL

Вы, наверное, задаетесь вопросом, как работают эти пользовательские сопоставители:

« `ожидание ruby ​​(страница). to have_mission_ Названо ‘Project Moonraker’ ожидание (page) .to have_completed_mission_name ‘Project Moonraker’

def has_mission_name? (mission_name)… конец

def has_completed_mission_named? (mission_name)… end « `

RSpec генерирует эти пользовательские сопоставления на основе методов предикатов на объектах Page. RSpec конвертирует их, удаляя ? и изменения должны have . Бум, спички с нуля без особого пуха! Немного магии, я дам вам это, но хороший вид волшебства, я бы сказал.

Поскольку мы припарковали наш объект Page по адресу specs/support/features/pages/missions.rb , вам также необходимо убедиться, что следующее не закомментировано в spec/rails_helper.rb .

ruby Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

Если вы NameError с NameError с uninitialized constant Pages , вы будете знать, что делать.

Если вам интересно, что случилось с методом sign_in_as , я извлек его в модуль по адресу spec/support/sign_in_helper.rb и сказал RSpec включить этот модуль. Это не имеет никакого отношения к объектам страницы напрямую, просто имеет смысл хранить функциональные возможности теста, такие как sign in , более доступным способом, чем через объект страницы.

Спецификация / поддержка / sign_in_helper.rb

ruby module SignInHelper def sign_in_as(email) visit root_path fill_in 'Email', with: email click_button 'Submit' end end

И вам нужно сообщить RSpec, что вы хотите получить доступ к этому вспомогательному модулю:

спецификации / spec_helper.rb

« `ruby… require ‘support / sign_in_helper’

RSpec.configure do | config | config.include SignInHelper… end « `

В целом, легко увидеть, что нам удалось скрыть особенности Capybara — такие как поиск элементов, нажатие на ссылки и т. Д. Теперь мы можем сосредоточиться на функциональности, а не на фактической структуре разметки, которая теперь инкапсулирована в объекте страницы. — структура DOM должна быть наименьшей из ваших проблем, когда вы тестируете что-то такое же высокое, как спецификации функций.

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

Есть две разные точки зрения на эту тему. Сторонники размещения утверждений в объектах Page говорят, что это помогает избежать дублирования утверждений. Вы можете предоставить лучшие сообщения об ошибках и добиться лучшего стиля «говори, не спрашивай». С другой стороны, сторонники объектных объектов без утверждений утверждают, что лучше не смешивать обязанности. Предоставление доступа к данным страницы и логике утверждения — это две отдельные проблемы, которые приводят к раздутым объектам страницы при смешивании. Ответственность за объект страницы — доступ к состоянию страниц, а логика утверждений принадлежит спецификациям.

Компоненты представляют наименьшие единицы и более сфокусированы — например, объект формы.

Страницы объединяют больше этих компонентов и являются абстракциями полной страницы.

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

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

Таким образом, вы, вероятно, не должны начинать писать объекты страницы сразу. Вы подходите к этим рефакторингу постепенно, когда сложность и размер вашего приложения / тестов растут. Дубликаты и рефакторинги, которые нуждаются в улучшении дома с помощью Page Objects, со временем будет легче обнаружить.

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

Объекты страницы дают вам возможность написать более четкие спецификации, которые читаются лучше и в целом намного более выразительны, потому что они более высокого уровня. Кроме того, они предлагают хорошую абстракцию для всех, кто любит писать ОО-код. Они скрывают специфику DOM, а также позволяют вам иметь закрытые методы, которые выполняют грязную работу, будучи недоступными общедоступному API. Извлеченные методы в ваших спецификациях не дают такой же роскоши. API-объектам Page Objects не нужно делиться мельчайшими подробностями капибары.

Для всех сценариев, когда меняются реализации проекта, ваши описания того, как должно работать ваше приложение, не должны изменяться при использовании объектов страницы — ваши спецификации функций больше сосредоточены на взаимодействиях на уровне пользователя и не слишком заботятся о специфике реализации DOM. Поскольку изменения неизбежны, объекты Page становятся критически важными при расширении приложений, а также помогают понять, когда размер приложения значительно увеличивает сложность.