Request spec in Action

    Тестирование стало неотъемлемой частью любой разработки программного продукта, будь то приложение под настольный компьютер, мобильное устройство или web. Уже никто не отрицает важность это этапа и последствий, которые принесет его отсутствие. Среди них большое время на проверку каждого элемента (страницы), и неожиданные сюрпризы в поведении продукта, увеличение затрат на исправление программы. Принцип написания тестов достаточно прост – «желтый цвет», «красный цвет», «зеленый цвет», рефакторинг. Где желтый цвет – это не созданный тест (pending), красный цвет – не прошедший тест, а зеленый – системе работает как надо.

    Для каждого вида программирования, существует множество типов тестирования. Но есть и общие моменты, присутствующие везде. Так как основной род моей работы является создание web приложений под ROR, поговорим про особенности тестирования данных приложений.


    Настройка среды


    Вы можете найти множество сред для тестирования (Test Unit, Rspec, Cucumber & etc), но лично я активно использую Rspec. Он позволяет придать тестам красивый вид, но при этом сохранить вид кода. В gemfile добавим новую группу:

    group :development, :test do
    gem "rspec-rails", ">= 2.5"
    end


    Вы можете удивиться и спросить: «Зачем добавлять rspec в development режим?» Все достаточно просто – rspec добавляет при генерации model, controller, scaffold соответствующие spec-и. Установив gem, проведем последнюю команду установки:

    rails g rspec:install

    Теперь мы можем запускать наши тесты по команде rake rspec. Отрицательная сторона данного подхода заключается в том, что при малейшем изменении нашего проекта нам снова и снова придется выполнять эту команду, которая будет продергивать абсолютно все тесты. Отсюда следует, что необходимо найти какой-нибудь watcher, чтоб он вместо нас выполнял необходимые команды. На данный момент я знаю про два неплохих решения – autotest и guard. Autotest пришел в rails с других framework, хорошо зарекомендовав себя, и раньше я использовал только его. Но недавно я познакомился с прелестным решением в виде guard. Сам по себе guard не несет в себе необходимый функционал, но при использовании его с дополнениями получается очень шикарная вещь. Этих дополнений у guard-а уже около 2 десятков, и они несут в себе огромные возможности. Однако остановимся пока на одном – guard-rspec. Установим необходимые файлы:

    group :test do
    gem ‘guard-rspec’
    end


    Выполним команду инициализации:

    guard init .
    guard init rspec


    Первая создаст guardfile (файл конфигурации наблюдателя), вторая добавит специфичные для rspec действия. Теперь по команде guard у вас работает watcher, готовый в любой момент продернуть необходимый spec. Но лог у данного решения достаточно скучный, поэтому добавим ему немного красок. Сперва, установим библиотеки уведомлений. И тут мы сталкиваемся с первыми проблемами – у вас с напарником разные ОС (Mac OS и Linux), а тестировать нужно всем. Выходом для нас послужило следующее решение:

    gem 'libnotify' , :require => false if RUBY_PLATFORM =~ /linux/i
    gem 'rb-inotify', :require => false if RUBY_PLATFORM =~ /linux/i
    gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i


    Далее, заменим все точки в rspec на процентное исчисление:

    gem 'fuubar'

    И, наконец, в Guardfile пропишем следующее:

    guard 'rspec', :cli =>'--format Fuubar --color'

    Запустим прохождение тестов. В консоли получаем красивую строку прогресса, а при завершении теста — всплывающее окно.

    Следующим шагом для настройки среды тестирования становиться придание чистоты нашим тестам. Ведь никто не хочет, чтобы при выполнении тестов нам попадались значения и записи в базе данных от предыдущих проверок. Хорошим выбором в данном случае может быть gem database_cleaner. Его конфигурирование приведено ниже:

    config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
    end

    config.before(:each) do
    DatabaseCleaner.start
    end

    config.after(:each) do
    DatabaseCleaner.clean
    end


    После настройки чистильщика, неплохо поговорить про создание тестируемого контента. Rspec предлагает использовать mock-и, но лично я предпочитаю генерировать реальные объекты. Для создания различных объектов можно использовать machinist или factory_girl. С machinist я особо не знаком, поэтому ничего про него сказать не могу. Но вот с factory_girl работаю достаточно давно, и нареканий gem никогда не вызывал. Добавим наши gem-ы в группу test:

    group :test do
    gem ‘guard_rspec’
    gem ‘factory_girl_rails’
    gem ‘database_cleaner’
    end


    Остальное использование я предлагаю рассмотреть вне этой статьи по ссылке

    Само тестирование


    Установив базовую среду тестирования, можно поговорить и про его особенности. Rspec разделяет все тестирование по частям, то есть в папке spec создаются копии папок раздела app. За мой скромный период разработки и тестирования, я нашел целесообразным только разделы models, helpers, mailers. И тут же возникает вопрос: «А что делать с важнейшими составляющими controllers и views?» На мой взгляд, решение, которое предлагает rspec, не совсем полноценно, и оно создает не совсем реальную ситуацию происходящего. Поэтому для своей разработки я выбрал тестирование запросов и полученных ответов. Для этого установим capybara. Автор библиотеки (Jonas Nicklas) описывает её так:

    «Capybara aims to simplify the process of integration testing Rack applications, such as Rails, Sinatra or Merb. Capybara simulate show a real user would interact with a web application.»

    Что дословно звучит как:

    «Морская свинка Capybara упрощает процесс комплексного тестирования, симулируя поведения пользователя в вашем приложении»

    Добавим его в gemfile. После в файле spec/test_helper.rb нужно добавить библиотеки Capybara:

    require 'capybara/rspec'

    Теперь наши тесты наполняются многими функциональными и удобными методами тестирования и манипуляцией с виртуальной страницей. Начнем по порядку:

    Посещение страницы выполняется понятным методом visit:

    visit ‘/’

    Кроме строчных адресов, метод принимает rails path helper:

    visit car_wheels_path(car)

    Но основная функциональность библиотеки заключается в доступном объекте page. Данный объект представляет собой виртуальную страницу для тестирования. Для получения её содержимого необходимо лишь обратится к методу body:

    puts page.body

    Давайте пройдемся по списку команд манипуляций. Текстовые и парольные поля заполняются методом fill_in, который принимает первый параметром id, class, name элемента, а параметр with задает содержимое:

    fill_in ‘car_manufacture’, :with=>’Audi’
    fill_in ‘car[model]’, :with=>’A4’


    Для выбора варианта в select используется метод select (спасибо, Кеп), который сначала принимает значение, а потом уже элемент для манипуляции:

    select ‘Silver’, :from=>’car[color]’

    Также существуют методы check, uncheck, choose, цель которых – изменение состояния checkbox и radio button. Они принимают лишь идентификатор элемента:

    check ‘car[full_package]’
    choose ‘car[year][2010]’


    Но бывает так, что в странице существует несколько элементов с одинаковыми name, или, не дай Бог, id. Для этого существует блоковый метод within, который ограничивает поиск внутри указанного элемента:

    within ‘form#payment_card’ do
    #много манипуляций
    end


    Для нажатия кнопок и ссылок существует несколько методов — универсальный (click_on) и специфические (click_link, click_button):

    click_on ‘submit_form’
    click_link ‘read_more’


    Теперь перейдем к проверки полученной структуры. Для проверки присутствия DOM элемента присутствует метод have_selector:

    page.should have_selector('table tr')

    А что если необходимо проверит количество этих элементов? То и тут все достаточно просто – добавим параметр count:

    page.should have_selector(‘table tr’, :count=>3)

    В ситуациях, когда элемент сам не фажен, а важно лишь содержимое пригодится метод page.have_content?

    page.should have_content(“Audi club”)

    А как же js?


    Написав пару spec-ов, вы уже заметили, что capybara полностью игнорирует js скрипты. Этот шаг разработчики сделали из-за сохранения быстродействия при прохождении тестов. Когда же есть необходимость проверить нашу страницу с полным функционалом, задействуем параметр js spec-а:

    it ‘should have many js’, :js=>true do
    visit …
    end


    Пред нами появится окно любимого браузера, откроется тестируемая страница, и курсор сам проделает все движения. Просто магия, но давайте разберемся. На самом деле capybara просто переключил web driver для обработки тестовой страницы. По умолчанию используется rack driver, который оперирует только полученным с ответом контентом. При использовании параметра js capybara начинает обращаться с selenium driver. Selenium запускает системный браузер, если не указан другой, и выполняет все желаемые действия. Все хорошо и красиво, кроме пары моментов. Во-первых, время выполнения теста становится очень долгим. И не удивительно, кроме загрузки самой среды rails, тест запускает браузер, плюс выполнение манипуляций также требует определённого времени. Во вторых, меня не устраивало постоянное отображение браузера. Поэтому мне пришлось отказаться от selenium в пользу альтернативного web driver-а – capybara-webkit. Данный gem специально писался под решение данных проблем. Для установки библиотеки, кроме самого capybara-webkit, необходимо установить ещё qt библиотеки. После в spec_helper укажем какой driver использовать для страниц с javascript:

    Capybara.javascript_driver :webkit

    Теперь интегральные тесты capybara будет выполнять быстро и без лишней анимации.

    Другим интересным моментом, с которым вы столкнётесь, будет проверка элементов, которые загружаются c помощью ajax. Методы have_selector и have_content не всегда проходят, поэтому используем такие методы как all, find, first:

    first(‘div.info’).should be

    all(‘tr.ads’).should have(3).items


    Также методы find и first могут пригодиться для эмуляции кликов на элементы отличные от кнопок и ссылок.

    first(‘div.catalog’).click
    find(‘p#info).click


    Если вам захотелось выполнить какой-нибудь скрипт, то capybara и тут вам пригодится:

    page.execute_javascript “alert(‘Hello world’)”

    Первоначально это кажется полной бессмыслицей – «Ну какой смысл выполнять в тестах js?» И ответом станет ситуация, когда необходимо проследить поведение страницы на действия отличные от click и select:

    page.execute_javascript ‘$(“p.long_text”).hover();’

    Вот так мы проэмулировали наведение на параграф курсора.

    Пару наблюдений из жизни

    И в конце моего поста пара советов из личного опыта. При использовании capybara, database_cleaner и factory_girl с любым js web driver, созданные ресурсы не буду отображаться на тестовой странице. Исправить данную ситуацию следующая настройка:

    config.use_transactional_fixtures = false

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

    config.filter_run :focus=>true
    config.run_all_when_everything_filtered = true


    После, для выполнения интересующих действий, достаточно будет добавить именной параметр :focus=>true и список spec ов будет отфильтрован.

    Полезные ссылки


    Capybara
    Capybara Webkit
    Data base cleaner
    Factory girl rails
    Guard
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 17
    • 0
      а зачем ему знать? он платит за результат вам
      вы экономите время => заказчик счастлив
      • 0
        Дело даже не в том, чтоб он заплатил, а чтоб понял задержки по времени связанные с написанием тестов
        • 0
          Никак не объяснить. Заказчику важен результат, а не то, что там «внутри».
          • 0
            думал у других подругому, как я ошибался…
      • 0
        Почему не использовать для такого вида тестирования cucumber+capybara, а rspec покрывать бизнес логику?
        Как мне известно, rspec как раз для тестировании бизнес логики заточен.
        • 0
          Это скорее мои личные предпочтения. Плюс я не очень глубоко знаком с cucumber
        • 0
          А как насчет pivotal.github.com/jasmine/? Оно вроде набирает обороты неплохо так.
          • 0
            тоже неплохо, но jasmine больше подходит для тестирования business логики js, чем общей картины поведения страницы
          • 0
            хм… там есть тот же самый функционал, что у Capybara. Фактически это замена селениума.
            • 0
              Это типа был ответ на «тоже неплохо, но jasmine больше подходит для тестирования business логики js, чем общей картины поведения страницы» :)
              • 0
                Я понял. Возможно вы правы, но я использовал jasmine лишь для тестирования js
            • 0
              «вкшмук» — это 5 (-:

              Отличная статья, но, моё мнение, может стоило добавить про spork? Когда тестов много, они занимают много времени…
              • 0
                Спасибо за ошибку. Насчет spork — уже подумываю добавить в проекте с более 40 тяжелыми request, но пока ограничиваюсь focus
                • 0
                  spork не ускоряет прогон тестов, ускоряется загрузка окружения
                  для ускорения методом распараллеливания есть guard-hydra, но он похоже еще сыроват
                • 0
                  Jonas Nicklas кстати скоро приезжает в Киев на конференцию rubyc :)
                  • 0
                    сам еду туда ради него да Steve Klabnik
                  • 0
                    Хочу заметить, что установить rb-fsevent под Lion довольно проблематично. На это несколько issue открыто, например вот эта: github.com/thibaudgg/rb-fsevent/issues/20

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

                    Самое читаемое