пятница, 1 ноября 2013 г.

Что такое CDN и для чего она нужна


CDN это сеть доставки контента (Content Delivery Network). Под словом контент в данном случае подразумеваются статические ресурсы, которые мы используем на сайте: JavaScript, стили (CSS), картинки, flash (SWF) файлы, и другие документы.

А под словами Сеть доставки подразумевают набор серверов разнесенных географически (географически распределённая сетевая инфраструктура). Файлы на таких серверах синхронизируются и при необходимости пользователя получить файл – сервера между собой решают, кто будет отдавать файл. 
А зачем?
  1. Экономия траффика (кэширование). На серверах CDN очень хорошо работает кэширование файлов. Например, пользователь зашел на сайт1, а потом на сайт2 и после этого зашел к нам на сайт. Если все три сайта используют один и тот же CDN, то библиотека загрузится только один раз, а все остальные разы она будет браться из локального кэша. Пользователь сэкономит несколько килобайт траффика и порадуется тому, как у него быстро загружаются сайты.
  2. Увеличение скорости загрузки (за счет снижения roadTime тк мы сокращаем расстояние между сервером и клиентом)
  3. Отсутствие на домене хранилища CDN каких-либо Cookies (размер запроса уменьшается).

Браузерный кеш

Жесткое кеширование (200 cache)





Для жесткого кеширования  достаточно добавить заголовки Expires и Cache-Control: max-age.

Для статических компонентов применяется часто правило "без срока давности", устанавливая заголовок Expires (срок жизни кеша) далеко вперед.

Или же наоборот если мы не хотим кешировать страницу то стоит установить в Expires прошедшую дату.


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

Есть еще один мета-тэг Pragma: no-cache довольно хорошо поддерживаемое соглашение, которому следует большинство web-браузеров. Обнаружив этот тэг, они обычно не кэшируют страницу (хотя никаких гарантий нет, это всего лишь соглашение).


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

Простое кеширование - HTTP Last-Modified, ETag (304)


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

If-Modified-Since: data,time

Сервер отвечает кодом статуса 304 (not modified/not match) и загружает страницу из кеша в следующих случаях:
  • Если браузер послал заголовок "If-Modified-Since: date", в котором date больше либо равна значению заголовка ответа "Last-Modified: date". Т.е. клиент спрашивает: "Есть ли новая версия с момента date? Если нет, верни мне 304 и сэкономь трафик. Если есть, отдай мне тело страницы".
  • Если браузер послал заголовок "If-None-Match: hash", где hash совпадает со значением заголовка ответа "ETag: hash". Т.е. клиент спрашивает: "Отличается ли текущая версия страницы от той, что я запросил в прошлый раз? Если нет, верни мне 304 и сэкономь трафик. Если да, отдай тело страницы".
Отличие в том что в etag нет привязки ко времени и в хеше мы можем передавать все что угодно. 
Срок жизни браузерного кеша - сессия (до тех пор пока браузер не перезапустить)




304 это не так быстро, как 200 (cache), тк запрос все равно передается на веб-сервер, но сервер не должен отправить содержимое файла.



понедельник, 6 мая 2013 г.

Capistrano

Capistrano — это Open Source-ный инструмент для выполнения скриптов на нескольких серверах, который в основном используется для web приложений. Он позволяет автоматизировать процесс развертывания новой версии на одном или нескольких web серверах и включает поддержку таких задач, как например изменение базы данных.
Capistrano написан на Ruby и является «модулем» фреймворка Ruby on Rails.

Итак, допустим на нашем локальном компьютере  находится приложение(папка с файлами). У этого приложения есть git репозиторий на гитхабе с актуальным кодом. Также у нас есть хостинг  с ssh доступом, куда мы собираемся залить наши файлы. Мы не хотим постоянно возиться с ftp клиентом и решили потратить несколько часов для того, чтобы разобраться в деплойере capistrano.


Капификация


Первое что мы должны сделать после установки capistrano это «capify-нуть» наше приложение. «Капификация» — это процесс конфигурации capistrano для развертывания приложения. Он достаточно прост: убедитесь что вы находитесь в корневой директории вашего проекта и введите

  $ cd /path/deploy/from
  $ capify .

Эта команда создаст два файла:
Capfile — главный файл который нужен capistrano. Capistrano по умолчанию ищет и исполняет «Capfile». Изначально генерируемый Capfile очень прост: все что он делает — загружает «config/deploy.rb»
config/deploy.rb — файл в котором содержатся «настройки» приложения (рецепт)


Структура директорий Capistrano


Проект успешно развернутый с Capistrano будет иметь структуру похожую на приведенную ниже ( где [deploy_to] — это каталог куда мы хотим развернуть проект):

    [deploy_to]
    [deploy_to]/releases
    [deploy_to]/releases/20080819001122
    [deploy_to]/releases/...
    [deploy_to]/shared
    [deploy_to]/shared/log
    [deploy_to]/shared/pids
    [deploy_to]/shared/system
    [deploy_to]/current -> [deploy_to]/releases/20100819001122

Каждый раз когда вы будете разворачивать проект в папке «releases» будет создаваться новая директория, в которую будет копироваться его последняя версия. После этого символическая ссылка «current» обновиться и будет указывать на новую директорию. 


Multistage


В разработке также можно использовать расширение capistrano-ext, которое позволяет делать так называемый multistage. Предположим у вас есть тестовый и рабочий сервера. Вы можете написать отдельный конфиг для каждого из них и выполнять deploy только для нужного сервера.
Создаем свои рецепты: например production.rb и staging.rb. Всё что нам нужно для конфигурации — это написать в файл config/deploy.rb две строки:

    set :stages, %w(staging production)
    require 'capistrano/ext/multistage'

Теперь вы можете выполнять деплой с помощью команд «cap production deploy» и «cap staging deploy». 

Хорошая статья в догонку http://wiki.agiledev.ru/doku.php?id=automation:build_package_deploy


среда, 27 марта 2013 г.

Parralel_tests

Тесты, просто чудесная вещь, но как и многое чудесное имеет свои недостатки. Главный из них – они очень долго выполняются. 


Parralel_tests

parallel_tests – инструмент, позволяющий распараллелить выполнение тестов (test unit), спек (rspec) или фич (cucumber). Надо понимать, что он запускает параллельно разные файлы, но не отдельные тесты внутри одного файла. Поэтому ускорить выполнение одного большого спека вам не удастся.
Всё, что вам нужно делать, это создать БД как сказано в README, а также после каждой миграции делать rake parralel:prepare.
Запускать можно как с параметрами по-умолчанию rake parallel:spec (в этом случае количество процессов определится автоматически по числу ядер), так и указав количество процессов и путь: rake parallel:spec[4,models].
parralel_test для Rspec запускаются командой parallel_spec или bundle exec parallel_spec (т.е. +30 секунд экономии по сравнению с rake).
Если используете parralel_test, то оптимальным числом процессов является значение по-умолчанию (т.е. число ядер)
При выполнении одного теста, т.е. в рядовой работе, parralel_test не даст выигрыша вообще
Есть одно немаловажное НО
Он не будет работать на Windows.
Опирается на DeepTest, который в свою очередь использует  Kernel.fork () ..., который не реализован на Windows.

среда, 27 февраля 2013 г.

Drag and drop - selenium ruby rspec!

Итак спустя 2 дня таки вышло реализовать drag and drop.
Моя конфигурация - webdriver 2.30.0 , ff 18.0.2

И собственно сам кодище))
Причем оба варианта работают, а вот без слипов беда - приходится их везде дописывать
Остальной код не стала подчищать ибо может тоже пригодиться))


# encoding: utf-8
require_relative '../lib/login_page'


describe "Draga" do
  before (:all) do
    @page = LoginPage.new(@browser, true)
    @page.login_user
  end
  it "do it" do
    @page.navigate_to("http://dfghh.test-pul.ru/articles/order")
    element1=@browser.find_element(:css, 'div.tip')
    element2= @browser.find_element(:xpath, '//div[text()="sdfsf"]')
    element3=@browser.find_element(:link,"Отменить")
    el= @browser.find_element(:xpath, '//span[text()="Оформление сайта"]')
    puts el.location
    @browser.mouse.move_to(el)
    sleep 3
    puts element1.location
    puts element2.location
    @browser.action.click_and_hold(element1).move_to(element2).release.perform
    sleep 3
    @browser.action.drag_and_drop(element1, element2).perform

    sleep 3
    #@browser.mouse.click(element3)
    @browser.action.click(element3).perform

    sleep 3
    @page.current_url.should_not include "order"
    sleep 3
  end
end 

вторник, 19 февраля 2013 г.

Причесываю тесты следуя правил


1. Дублирующиеся тесты в разных контекстах можно вынести в shared_examples (https://www.relishapp.com/rspec/rspec-core/v/2-12/docs/example-groups/shared-examples!), например так:
shared_examples_for "страница с ошибками во всех полях" do
  it { @login_page.should be_errors }
  it { @login_page.should be_error_wrong_fio }
  it { @login_page.should be_error_wrong_email }
  it { @login_page.should be_error_no_match_pass }
end
describe " фио цифрами" do
  it { ... }
  it_behaves_like "страница с ошибками во всех полях"
end
describe " фио на английском" do
  it { ... }
  it_behaves_like "страница с ошибками во всех полях"
end
shared_examples лучше все выносить в отдельный файлик и хранить его в папочке support
Если эти описания нельзя как-то логически объединить в один shared_examples_for, навроде "

регистрация с ошибками", то лучше использовать для каждого дискрайба отдельные, пусть даже повторяющиеся, но понятные it

2. в rspec принято все общие заголовочные вещи выносить в spec/spec_helper.rb и во всех спеках:
require 'spec_helper'
кроме того в нем можно предварительно установить общие для тестов переменные
RSpec.configure do |config|
  config.let!(:browser) { Selenium::WebDriver.for :ff }
end

среда, 23 января 2013 г.

Немного о githube


Удалённые репозитории — это модификации проекта, которые хранятся в интернете или ещё где-то в сети. Их может быть несколько, каждый из которых, как правило, доступен для вас либо только на чтение, либо на чтение и запись. Совместная работа включает в себя управление удалёнными репозиториями и помещение (push) и получение (pull) данных в и из них тогда, когда нужно обменяться результатами работы.

1) Есть "главный" репозиторий.  Как правило, в этом репозитории есть несколько веток. доступ к главному репозиторию по ключу, нужен аккаунт на гитхабе, в котором прописан ключ
https://github.com/abak-press/selenium_blizko

2) Каждый разработчик может создавать собственные ветки. Но чтобы не загаживать ими основной репозиторий, каждый из разработчиков должен работать в пределах собственного репозитория. Что значит "форкнуть" проект на гитхабе? Это значит создать полную копию репозитория. Что ж, форкнем его и получим еще один удаленный репозиторий :
https://github.com/grebenschikova/selenium_blizko


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

4) Вот мы и форкнули весь проект. Теперь нам нужно заиметь его на собственном компьютере. Сейчас будет создан еще один репозиторий (уже третий). Этот репозиторий будет условно "локальным". Для этого клонируем его себе на локальную машину из своего репозитория на гитхабе

git clone git@github.com:grebenschikova/selenium_blizko.git

Чтобы просмотреть, какие удалённые серверы у вас уже настроены, следует выполнить команду git remote. Она перечисляет список имён-сокращений для всех уже указанных репозиториев. Если вы склонировали ваш репозиторий, у вас должен отобразиться, по крайней мере, origin — это имя по умолчанию, которое Git присваивает серверу, с которого вы склонировали.

$ git remote
originЧтобы посмотреть, какому URL соответствует сокращённое имя в Git, можно указать команде  опцию -v:
$ git remote -v
origin  git@github.com:grebenschikova/selenium_blizko.git

Если у вас больше одного удалённого репозитория, команда покажет их все. 
$ git remote -v
origin  git@github.com:grebenschikova/selenium_blizko.git
upstream     git://github.com/abak-press/selenium_blizko.git

Это означает, что мы легко можем получить изменения от любого из этих пользователей. Но, заметьте, что origin — это единственный удалённый сервер прописанный как SSH-ссылка, поэтому он единственный, в который я могу помещать свои изменения
Чтобы добавить новый удалённый Git-репозиторий под именем-сокращением, к которому будет проще обращаться, выполните git remote add [сокращение] [url].

git remote add  upstream git://github.com/abak-press/selenium_blizko.git

Этой командой мы сказали "сделай у нашего текущего репозитория ссылку на удаленный репозиторий и для удобства дай этой ссылке понятное имя upstream . Теперь мы всегда будем знать что upstream   это ссылка на наш удаленный репозиторий.

Теперь вы можете использовать в командной строке имена origin и upstream вместо полного URL. Например, если вы хотите извлечь (fetch) всю информацию, которая есть в репозитории, но нет в вашем, вы можете выполнить git fetch upstream
Данная команда связывается с указанным удалённым проектом и забирает все те данные проекта, которых у вас ещё нет. 
Ветка master глобального репозитория теперь доступна локально как upstream/master. Вы можете слить (merge) её в одну из своих веток
Вы можете использовать команду git pull. Она  как правило, извлекает (fetch) данные с сервера, с которого вы изначально склонировали, и автоматически пытается слить (merge) их с кодом, над которым вы в данный момент работаете.

Для переименования ссылок в новых версиях Git'а можно вылолнить git remote rename, это изменит сокращённое имя, используемое для удалённого репозитория. Например, если вы хотите переименовать pb в paul, вы можете сделать это следующим образом:
$ git remote rename pb paul

5) Создаёте новую ветку в своем локальном репозитории (в форке наследуются ветки родителя, так что если форкаете то ветку можно не создавать - master уже будет)
git checkout -b feature


Чтобы посмотреть существующие ветки, введем:
git branch

6) Работаете, делаете коммиты, в случае необходимости отслеживания изменений в «родителе», сливаете изменения с него и вливаете в свою ветку таким образом:
git fetch upstream
git pull upstream master
git merge master

7) Когда работу сделали, заливаете изменения в свой github-репозиторий в свою ветку:
git add .

git commit
git push origin 

8) Теперь идёте на гитхаб, в свой репозиторий и жмёте вверху кнопочку «Pull request»

9) Слева выбираете в какую ветку будут вливаться изменения в родительском репозитории, справа — какие изменения будут браться с вашего репозитория. По примеру: справа abak-press/master, слева grebenschikova/master.

ВАЖНО: Договоритесь с владельцем «родительского» репозитория, в какую ветку будете вливать изменения (он может написать это в README)

10) Заполняете название и описание (название потом попадёт в описание мёрдж-коммита и станет достоянием общественности, учтите это).

11) Нажимаете Send Pull Request

Вуаля, вы его отправили. Владелец рассмотрит ваши изменения и, возможно, их примет и вольёт к себе.
На практике, лучше перед посылкой пулл-реквестов, вручную синхронизироваться с веткой, в которую будете посылать изменения, чтобы у владельца merge прошёл гладко (больше шансов, что пулл примут)
Не забудьте потом сделать git pull upstream master, чтобы увидеть изменения у себя.



Дописать - что делать после принятия пулреквеста.

среда, 12 декабря 2012 г.

Основы rspec



Как проверить равенство

a.should equal(b) # Для обоих if a.equal? b
a.should be(b) #
a.should eql(b) # passes if a.eql? b
a.should == b # Для обоих if a == b
a.should eq(b) #
аналогично неравенство a.should_not == b
Пример: 

String.should == "this is a string"
[1, 2, 3].should == [1, 2, 3]

Как проверить включение или соотношение

37.should be < 100
37.should be >= 2
"This is a string".should include "str"
# Два одинаковых выражения:
"This is a string".should =~ /^This/
"This is a string".should match(/^This/)

Как проверить истинность выражения

obj.should be_true # Проходит если obj - truе (не nil и не false)
obj.should be_false # Проходит если obj - false (nil или false)
obj.should be_nil # Проходит если obj - nil
obj.should be # Проходит если obj не nil


Как проверить cуществование элемента


[].should be_empty
[].should_not be_empty
test_result.should exist
test_result.should_not exist

Как завершить тест с ошибкой или просто вывести пользовательскую ошибку

expect { 4/2 }.to_not raise_error
expect { 4/0 }.to raise_error
expect { 4/0 }.to raise_error(ZeroDivisionError)
expect { 4/0 }.to raise_error(ZeroDivisionError, "divided by 0")
o.should raise_error 

it "includes 3" do
[1, 2, 3].should include(3), "Oh noes! No three!"
end

Как проверить количество элементов

[1, 2, 3].should have(3).items
[1, 2, 3].should have_exactly(3).items
[1, 2, 3].should have_at_least(2).items
[1, 2, 3].should have_at_most(4).items
[].should be_empty


Подробнее тут и тут

вторник, 11 декабря 2012 г.

CI

AS IS

По каждому пушу происходит деплой сервера (абстрактного) и запускается билд
Если билд еще не прошел то новый не запускается.

TO BE

Вопрос - какую схему выбрать:
1. тестировать проект на тестовом сервере
тогда после каждого пуша будет происходить деплой тествого - не очень удобно.
 2. тестировать на нашем сервере селениум - для этого надо там поднять проект. И аналогично запускать деплой по каждому пушу. (Нашему или программистов?? Скорее второе) Опять же в этом случае приходим к тому, что на тестовом и на сервере селениума будут разные сборки проекта, что не очень здорово

понедельник, 10 декабря 2012 г.

Скриншоты RubySelenium

@driver.save_screenshot("./screen.png")
Такой простой командой мы сохраняем скриншот.
Сохраняется скриншот всей страницы.

Бонус - можно менять размеры окна
@driver.execute_script %Q{window.resizeTo(#{width}, #{height});

ToDo:
1. Складывать скриншоты отдельно от репозитория
2. Генерить имя скриншота в формате дата+текущее временя
3. Периодически чистить папки со скриншотами (лучше бы  по крону)
4. Написать аналогичный faq по видео

пятница, 7 декабря 2012 г.

Думки

Мне кажется я никогда не дойду до логического конца в поисках идеального шаблона для наших автотестов...

Изначально я долго долго выбирала что же взять за основу - rspec, cucumber, capybara и тд - пришла к выводу что одного rspeca более чем хватит

Дальше Николай Алименков со своим докладом подтолкнул меня к мысли разделять логику данные и реализации. Активно занялась этим вопросом. Rspec это вполне позволяет делать.

Далее я начала копать в сторону локаторов - поняла что надо юзать page-object. Так же узнала про такие вещи как Page Factory, Element Object

Ну и из последних полезных вещей я наткнулась на генераторы случайных данных - такие как Выдумщик, Рыба, Ffaker - очень классная штука кстати.

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

вторник, 4 декабря 2012 г.

Особенности selenium


Элементы теста:
  1. Приложение - драйвер
  2. Тестовая логика (описание)
    -предусловия
    -сценарии
    -постусловия
  3. Технические детали (код)
  4. Тестовые данные

Основные принципы:
  1. Повторное использование кода
  2. Атомарные тесты
  3. DSL - разбиение на тестовую логику и технические детали (логика не зависит ни от переменных, ни от реализации)
  4. Справочник доменных понятий
Локаторы:

В отличае от сахи нет разделения на веб-элементы (span,textarea и тд)

Selenium предлагает семь встроенных способов поиска элементов:

Простые локаторы
  • по идентификатору элемента (значению атрибута id)
  • element = driver.find_element(:id, 'some-frame')
  • по имени элемента (значению атрибута name)
  • element = driver.find_element(:name, 'q')
  • по классу элемента(значению атрибута class)
  • element = driver.find_element(:class, 'news') (или по class_name)
    element = driver.find_element(:class_name, 'news') 
  • по тексту ссылки
  • element = driver.find_element(:link, "Зарегистрироваться") (или по link_text)
    element = driver.find_element(:link_text, "Зарегистрироваться")
    element = driver.find_element(:partial_link_text, "Регистрация") (ищется вхождение в текст ссылки)
  • по названию тега элемента
  •  element = driver.find_element(:tag_name, 'td') 
Сложные локаторы
  • по запросу XPath
  • element = driver.find_element(:xpath, "//div[@id='content']/*/span")
    element = driver.find_element(:xpath, "/html/body/div") - ищем от корня
    element = driver.find_element(:xpath, "//input") - ищем по всем вложениям элемент input
    element = driver.find_element(:xpath, "//*[@id=menu]") - ищем любой элемент с id menu
    element = driver.find_element(:xpath, "//menu")
    element = driver.find_element(:xpath, "//span[@class=test and @name=span]") и 
  • element = driver.find_element(:xpath, "//span [@class=test][@name=span]") или
  • element = driver.find_element(:xpath, "//a[text()='some text']")
    element = driver.find_element(:xpath, "//div[1]") или
    element = driver.find_element(:xpath, "//div[position()=1]")
    element = driver.find_element(:xpath, "//div[@id='test' and contains()='text']")
    element = driver.find_element(:xpath, "//div[@id='it']/*/a[counts()=1]")
    element = driver.find_element(:xpath, "/descendant::div[@id='my']/descendant::a[1]")
    Ищем среди потомков документы див my, а среди его потомков первую ссылку
    element = driver.find_element(:xpath, "//a[ancestor::div[@id='my']]")
    Ищем ссылку с родителем div (c id my)
  • element = driver.find_element(:xpath, "//*[contains(text(),'ABC')]")
  • по селектору CSS
  • обращаемся к элементам
  • element = driver.find_element(:css, "span.toolbar-link") по классу
    element = driver.find_element(:css, "span#news_class") по id
    element = driver.find_element(:css, "p") по тегу - находим все элементы p
    element = driver.find_element(:css, "*") все элементы на странице
  • обращается по атрибутам
  • element = driver.find_element(:css, "div[class=toolbar_menu]") полное совпадение
    element = driver.find_element(:css, "div[class^=toolbar]") начинается с
    element = driver.find_element(:css, "div[class$=menu]") оканчивается на
    element = driver.find_element(:css, "div[class]") div у которого есть атрибут class
    element = driver.find_element(:css, "div[class*=bar]") содержит текст
  • отношения элементов друг к другу
  • element = driver.find_element(:css, "div a") ищем все ссылки в дивах (все потомки в любой вложенности)
    element = driver.find_element(:css, "div > a") ищем ссылки в дивах (непосредственный потомки - между ними не должно быть других элементов)
    element = driver.find_element(:css, "div + div") ищем элемент сразу за элементом (между ними не должно быть других элементов)
    element = driver.find_element(:css, "div ~ div") ищем элемент за элементом (между ними могут быть другие элементы)
    element = driver.find_element(:css, "div:contains('text')") div содержит текст
    element = driver.find_element(:css, "div#menu a:nth-of-type(1)") Первая ссылка в диве с id menu
    element = driver.find_element(:css, "span[name=hello][background=green]") Ищем элемент  с 2 атрибутами
Метод, начинающийся со слов findElement находит первый элемент, удовлетворяющий условиям поиска, а метод, начинающийся с findElements – все подходящие элементы

Подробнее про локаторы для руби тут
Отличный вебинар по локаторам от Михаила Поляруша - ссылка
Подробнее про локаторы вцелом тут и тут
Про то как правильно подбирать локаторы - подробнее

Команды

вторник, 20 ноября 2012 г.

Как правильно писать тесты на RSpec?


Структура описания теста

Первое, что хочется отметить – это то, что блоки “описания” (describe) могут и должны быть вложенными, это позволяет достаточно подробно структурировать тест. При написании теста я всегда руководствуюсь тем, что при запуске rspec с ключом “-f s” должна отображаться готовая спецификация к тесту. Поэтому в блоке “описания” я описываю одну конкретную функцию, которая должна быть реализована (например, “сохраняет новую статью в базе данных”).

Обычно в первую очередь я создаю структур теста содержащего только блоки “описания” (describe), а только потом эти блоки наполняю их конкретными предположениями (блоками IT). Вот пример такой структуры:
describe "Article" do 
describe "структура" do
end

describe "сохранение статьи в БД" do
end

describe "удаление из БД по заданному ID" do
end

describe "поиск статьи по заданному id в БД" do
end
end

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

describe "Article" do
describe "структура" do
it "должна содержать поле title"
it "должна содержать поле content"
end
describe "сохранение статьи в БД" do
it "при длине поля title более 255 символов должно выбрасываться исключение"
it "при пустом поле title должно выбрасываться исключение"
it "при длине поля content более 65535 символов должно выбрасываться исключение"
it "при пустом поле content должно выбрасываться исключение"
it "при наличии в поле content стоп слов должно выбрасываться исключение"
it "при успешном сохранении статьи должен возвращаться объект типа Article"
end
describe "удаление из БД по заданному ID" do
it "при успешном удалении должно возвращаться значение TRUE"
it "при не успешном удалении должно выбрасываться исключение"
end
describe "поиск статьи по заданному id в БД" do
it "для существующего ID должен возвращаться объект Article"
it "для несуществующего ID должно выбрасываться исключение"
end
end

Хочу обратить внимание, что на данном этапе блоки IT не имеют реализации, поэтому при запуске теста они будут помечены как “(PENDING: Not Yet Implemented)”. В итоге запуск команды “spec -f s ./spec.rb” выдаст следующую спецификацию:

Article структура
- должна содержать поле title (PENDING: Not Yet Implemented)
- должна содержать поле content (PENDING: Not Yet Implemented)
Article сохранение статьи в БД
- при длине поля title более 255 символов должно выбрасываться исключение (PENDING: Not Yet Implemented)
- при пустом поле title должно выбрасываться исключение (PENDING: Not Yet Implemented)
- при длине поля content более 65535 символов должно выбрасываться исключение (PENDING: Not Yet Implemented)
- при пустом поле content должно выбрасываться исключение (PENDING: Not Yet Implemented)
- при наличии в поле content стоп слов должно выбрасываться исключение (PENDING: Not Yet Implemented)
- при успешном сохранении статьи должен возвращаться объект типа Article (PENDING: Not Yet Implemented)
Article удаление из БД по заданному ID
- при успешном удалении должно возвращаться значение TRUE (PENDING: Not Yet Implemented)
- при не успешном удалении должно выбрасываться исключение (PENDING: Not Yet Implemented)
Article поиск статьи по заданному id в БД
- для существующего ID должен возвращаться объект Article (PENDING: Not Yet Implemented)
- для несуществующего ID должно выбрасываться исключение (PENDING: Not Yet Implemented)

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

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

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

Из описания теста, указанного выше, видно, что на первый вопрос ответ дается в разделе “Article структура”, на второй вопрос отвечают названия всех остальных блоков describe, а не третий и четвертый вопросы отвечает содержимое блоков IT.

воскресенье, 18 ноября 2012 г.

Поднимаем сервер непрерывной интеграции

Selenium WebDriver – это не инструмент для автоматизации тестирования.
Итак, что такое Selenium WebDriver?

По назначению Selenium WebDriver представляет собой драйвер браузера, то есть программную библиотеку, которая позволяет разрабатывать программы, управляющие поведением браузера.

По своей сущности Selenium WebDriver представляет собой:
  • спецификацию программного интерфейса для управления браузером,
  • референсные реализации этого интерфейса для нескольких браузеров,
  • набор клиентских библиотек для этого интерфейса на нескольких языках программирования.

Теперь понятно, почему бессмысленно сравнивать Selenium WebDriver с «другими инструментами тестирования». Selenium WebDriver – это драйвер браузера

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

Так вот, Selenium WebDriver, или просто WebDriver – это драйвер браузера, то есть не имеющая пользовательского интерфейса программная библиотека, которая позволяет различным другим программам взаимодействовать с браузером, управлять его поведением, получать от браузера какие-то данные и заставлять браузер выполнять какие-то команды.

Исходя из этого определения, ясно, что WebDriver не имеет прямого отношения к тестированию. Он всего лишь предоставляет автотестам доступ к браузеру. На этом его функции заканчиваются.

Структурирование, группировку и запуск тестов, а также генерацию отчётов о тестировании, обеспечивает фреймворк тестирования в нашем случае RSpec или Cucumber для Ruby. Разработка тестов ведётся в среде RubyMine (либо любой текстовый редактор). Запуск тестов по расписанию и публикацию отчётов выполняет сервер непрерывной интеграции – CruiseControl. И всё это – самостоятельные инструменты, не имеющие отношения к проекту Selenium.


Впрочем, в рамках проекта Selenium разрабатывается не только драйвер, но ещё несколько сопутствующих продуктов – Selenium Server позволяет организовать удалённый запуск браузера, при помощи Selenium Grid можно построить кластер из Selenium-серверов. Они встают в один ряд с вышеперечисленными инструментами и фреймворками, потому что также участвуют в построении системы запуска тестов. Кроме того, имеется «рекордер», который называется Selenium IDE, он умеет записывать действия пользователя и генерировать код, в котором используется интерфейс WebDriver для выполнения записанных действий.

Популярные фреймворки для проектирования тестов позволяют наряду с другими драйверами использовать WebDriver. Среди таких фреймворков можно упомянуть Capybara

Серверная сторона вопроса

Для запуска интеграционных тестов потребуется:
1. Некоторая виртуальная машина ОС debian (Command-line interfaces), на которой установлено все необходимое для запуска нашего кода ПО (ruby, postgresql-client, libxml2, libxslt и т.д.)
2. Доступ до БД (для каждого проекта должна существовать некоторая пустая база)
3. X-сервер - (обеспечивает доступ по VNC к среда рабочего стола или GUI)
4. Firefox или Chrome
5. git
6. Место, куда можно склонировать из гита проекты

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