Автотесты на ruby с использованием готовых фреймворков: testgen

Как в доме важен фундамент, так и для автотестов важен хорошо продуманный фреймворк.

Представим относительно простое веб-приложение.

Сферическое веб-приложение в вакууме Сферическое веб-приложение в вакууме

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

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

  • фреймворка не было изначально, не было договоренностей о структуре проекта.

  • автоматизатора, особенно начинающего, оставили без присмотра и без код-ревью.

  • автоматизация изначально не была оправдана. Иногда лучшая автоматизация — это та, которую не начали.

С практической точки зрения, в сотый раз выдумывать похожую структуру тестового фреймворка для очередного веб-приложения, невыгодно. Здравый смысл подсказывает, что есть люди, у которых это получается гораздо лучше, и что еще важнее — они делятся своими наработками. Например, в разделе Frameworks на гитхабе представлено более 10 наименований для ruby.

К сожалению, не все из них достаточно освещены в публикациях, которые бы показали, насколько быстро и удобно создавать типовые ui-тесты на их основе.

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

TestGen

— сгенерирует все, что нужно тестировщику. Гем от Jeff Morgan, известного как cheezy. Кроме того, что он является создателем заточенных под тестирование гемов, Jeff Morgan также — автор книги "Cucumber & Cheese", которая сильно рекомендуется к прочтению. Глава за главой, книга-тренинг учит построению фреймворка из подручных гемов — разворачиванию каркаса проекта, генерации тестовых данных, "фишкам" руби и прочим важным вещам — на примере игрушечного веб-приложения.

Чтобы не повторять содержимое книги, соберем сгенерируем нестандартный (не по книге) фреймворк, включающий в себя:

  • testgen

  • capybara (вместо watir или чистого selenium)

  • cucumber

  • rspec

  • site_prism (вместо pageobject)

  • factory_girl и faker

и напишем тесты для регистрации на dropbox. Используемый браузер — chrome.

Шаг 1. Устанавливаем TestGen:

gem install testgen

Шаг 2. Создаем новый проект, пока еще с selenium и pageobject:

testgen project example_testgen --pageobject-driver=selenium

В результате создастся проект с простой структурой:

create exampletestgen
create example
testgen/cucumber.yml
create exampletestgen/Gemfile
create example
testgen/Rakefile
create exampletestgen/features
create example
testgen/features/support
create exampletestgen/features/stepdefinitions
create exampletestgen/features/support/env.rb
create example
testgen/features/support/hooks.rb
create example_testgen/features/support/pages

Если кратко, то предназначение сгенерированных файлов и директорий:

hooks.rb — для действий, которые выполняются до или после тестового сценария

cucumber.yml — для настроек (базовый url, дефолтный браузер, отчеты, перезапуск упавших тестов)

env.rb — сердце cucumber-проекта

features/ — для тестов (feature-файлы), реализации тестовых шагов (step definitions), важных файлов (env.rb, hooks.rb) и страниц (page objects).

Шаг 3. Удаляем все из hooks.rb, потому что собираемся использовать капибару вместо чистого селениума.

Можно удалить Rakefile, поскольку он не будет рассматриваться в данном примере.

Шаг 4. Редактируем Gemfile, добавляем новые гемы и устанавливаем их при помощи bundler.

source 'https://rubygems.org'
gem 'cucumber'
gem 'rspec'
gem 'site_prism'
gem 'capybara'
gem 'factory_girl'
gem 'faker'

Шаг 5. Cоздаем файл features/support/factories/user.rb. Он понадобится для генерации тестовых пользовательских данных.

FactoryGirl.define do factory :user, class: OpenStruct do Faker::Config.locale = :en first_name Faker::Name.first_name last_name Faker::Name.last_name email "nn24086+#{Time.now.to_i}@gmail.com" password Faker::Internet.password(min_length = 6) end factory :user_blank_name, class: OpenStruct, parent: :user do first_name '' end factory :user_blank_email, class: OpenStruct, parent: :user do email '' end
factory :user_invalid_email, class: OpenStruct, parent: :user do email 'nn24086gmail.com' end factory :user_blank_password, class: OpenStruct, parent: :user do password '' end factory :user_week_password, class: OpenStruct, parent: :user do password Faker::Internet.password(max_length = 5) end end

Шаг 6. Создаем файл boot.rb в корне проекта, чтобы не указывать "require_relative" для каждого файла из support/.

Dir.chdir(File.join(File.dirname(__FILE__), '.'))

Dir[File.join(File.dirname(__FILE__), './pages/**/*.rb')].each { |f| require f }

Dir[File.join(File.dirname(__FILE__), './factories/**/*.rb')].each { |f| require f }

Шаг 7. Редактируем env.rb:

require 'rspec'
require 'site_prism'
require 'capybara'
require 'capybara/cucumber'
require 'capybara/dsl'
require 'capybara/rspec/matchers'
require 'factory_girl'
require 'faker'
require_relative '../../boot'
World(Capybara::DSL)
World(Capybara::RSpecMatchers)
include FactoryGirl::Syntax::Methods

Указываем капибару и хром по дефолту:

Capybara.register_driver :chrome do |app| Capybara::Selenium::Driver.new(app, :browser => :chrome) end

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

Capybara.register_driver ENV['BROWSER'].to_sym do |app| Capybara::Selenium::Driver.new(app, :browser => ENV['BROWSER'].to_sym) end

Capybara.configure do |config| config.run_server = false config.app_host = ENV['TEST_HOSTNAME'] config.default_driver = ENV['BROWSER'].to_sym config.default_driver = ENV['BROWSER'].to_sym end

Добавляем строку, чтобы окно браузера открывалось в максимальном размере:

Capybara.page.driver.browser.manage.window.maximiz

Шаг 8. Редактируем cucumber.yml:

default: TEST_HOSTNAME=http://www.dropbox.com BROWSER=chrome --no-source --color --format pretty --profile html_report --profile rerun html_report: --format progress --format html --out=report.html
rerun: -f rerun --out rerun.txt

Шаг 9. Создаем тест в features/sign_up.feature

Feature: Dropbox sign up In order to use dropbox As a registered user I need to sign up

Background: Given I am on the Dropbox sign up page

Scenario: Sign up with valid credentials Given I have valid credentials When I fill the sign up form and submit Then account should be created

Scenario: Sign up with blank name Given I have credentials without first name When I fill the sign up form and submit Then I should see error notification "Please enter your first name" And account should not be created

Шаг 10. Описываем элементы на странице, используя siteprism: features/support/pages/signup_page.rb

class SignUpPage < SitePrism::Page set_url "/register" set_url_matcher /dropbox.com\/register/

element :first_name, 'input[name="fname"]' element :last_name, 'input[name="lname"]' element :email, 'input[name="email"]' element :password, 'input[name="password"]' element :agreement, 'input[name="tos_agree"]' element :create_account, 'form:first-of-type > button[class*="login"]' element :error_message, 'span[class="error-message"]'

def fill_form_with(user) first_name.set user.first_name last_name.set user.last_name email.set user.email password.set user.password end

def set_agreement_checkbox agreement.click end

def submit_form create_account.click end

def error_message_text error_message.text end end

Шаг 11. Реализация шагов из теста в features/stepdefinitions/signup_steps.rb :

Given(/^I am on the Dropbox sign up page$/) do @sign_up_page = SignUpPage.new @sign_up_page.load end

Given(/^I have valid credentials$/) do @user = FactoryGirl.create :user end

Given(/^I have credentials without first name$/) do @user = FactoryGirl.create :user_blank_name end

When(/^I fill the sign up form and submit$/) do @sign_up_page.fill_form_with(@user) @sign_up_page.set_agreement_checkbox @sign_up_page.submit_form end

Then(/^account should be created$/) do pending # Write code here that turns the phrase above into concrete actions end

Then(/^account should not be created$/) do pending # Write code here that turns the phrase above into concrete actions end

Then(/^I should see error notification "([^"]*)"$/) do |text| expect(@sign_up_page).to have_error_message expect(@sign_up_page.error_message_text).to include text end

Шаг 12. Запуск тестов:

cucumber

В результате:

Using the default, html_report and rerun profiles... Feature: Dropbox sign up
In order to use dropbox As a registered user I need to sign up

Background: Given I am on the Dropbox sign up page

Scenario: Sign up with valid credentials Given I have valid credentials When I fill the sign up form and submit Then account should be created TODO (Cucumber::Pending) ./features/step_definitions/sign_up_steps.rb:21:in `/^account should be created$/' features/sign_up.feature:12:in `Then account should be created'

Scenario: Sign up with blank name Given I have credentials without first name When I fill the sign up form and submit Then I should see error notification "Please enter your first name" And account should not be created TODO (Cucumber::Pending) ./features/step_definitions/sign_up_steps.rb:24:in `/^account should not be created$/' features/sign_up.feature:18:in `And account should not be created'

2 scenarios (2 pending) 9 steps (2 pending, 7 passed)
0m13.827s
Report — report.html

Перезапуск упавших тестов.

Список упавших тестов сохраняется в rerun.txt. Перезапуск тестов доступен по команде cucumber @rerun.txt.

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

Продолжение следует.