среда, 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.