Тестовый движок Cucumber (Ruby)


(Mykhailo Poliarush) #1

Зачастую решение по автоматизированному тестированию становится достаточно сложным, что для написания тестов и всего вспомогательного кода требуются навыки программирования, причем иногда даже не начального уровня, а немного повыше. В то же время, имеет место тенденция к упрощению работы с автотестами. Это достигается путем добавления различных визардов, созданию определенных подходов и движков, реализующих данный подход. В частности популярен т.н. keyword-driven подход, когда тесты представляют собой последовательность ключевых слов, структурированную в таблицах. Но есть еще более заковыристый подход, при котором тесты представляются в виде обычных инструкций, написанных в виде обычного текста, для которых есть определенная реализация на уровне программного кода. Для Ruby есть такой движок, именуемый Cucumber.

Итак, допустим, у нас есть некоторый тестовый сценарий, описанный в виде:

  • Login into the system
  • Click on "Search" button
  • Search screen is displayed

Сразу стоит отметить, что сценарий взят с потолка. Мы рассматриваем некоторое "сферическое приложение в вакууме", у которого есть функциональность для залогинивания, а также есть некоторый функционал поиска. Допустим, на Ruby этот тест реализуется в виде:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }app.login
app.click_button "Search"
assert app.search_screen_displayed? , "No search screen available"{/syntaxhighlighter}

Исходим из предположения, что весь необходимый для выполнения данного сценария функционал у нас есть. Вот так вот компактно можно реализовать данный тест. Но это просто и легко для тех, кто программирует на Ruby. А теперь представим, что у нас есть тест-дизайнер, который просто пишет сценарии. Как сделать так, чтобы ему можно было создавать тесты, которые можно было бы запускать автоматически? Вот подобными вещами Cucumber и занимается.

Что это такое? По сути - это текстовый парсер, который оперирует определенными текстовыми конструкциями и определенному тексту ставит в соответствие некоторый блок программного кода на Ruby. Этот модуль предоставляется как отдельный gem. Чтобы его установить надо выполнить командную строку:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }gem install cucumber{/syntaxhighlighter}

Итак, исходя из определения, у нас есть обычный текст теста и поставленные в соответствие определенным фразам блоки кода. То есть имеет место разделение описания и реализации. В Cucumber это выражается в том, что описание тестов хранится в файле с расширением *.feature, а сама реализация находится в файле с расширением *.rb. Более того, файлы формируются исходя из определенной структуры каталогов. Вообще, Cucumber работает со структурой сформированной по следующим правилам:

  • Главный каталог, содаржащий описание тестов и их реализацию именуется features именнов этом каталоге и во всех вложенных каталогах могут находиться feature-файлы
  • Все Ruby-реализации инструкций находятся в подкаталоге features/step_definitions и во всех вложенных подкаталогах
  • Вспомогательный функционал находится в подкаталоге features/support
  • В подкаталоге features/support находится файл env.rb, который отвечает за функционал, выполняемый до/после тестов, какие-то настройки и т.п. Это отдельная тема.

Итак, определившись со структурой, можем создать определение нашего теста. В каталоге features создадим файл sample.feature и напишем определение теста вот так:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }Feature: Cucumber sample testing
Scenario: Sample scenario
Given Login into the system
When Click on "Search" button
Then Search screen is displayed{/syntaxhighlighter}

Вот пример описания теста в Cucumber. Здесь жирным шрифтом отмечены 5 ключевых слов (это не все ключевые слова, но наиболее часто используемые):

  • Feature: - просто содержит описание группы тестов
  • Scenario: - предшествует имени теста
  • Given - обозначает, что инструкция за этим словом - это некоторые предварительные настройки или начальное состояние
  • When - обозначает, что инструкция за этим словом - это некоторые некоторое действие, которое надо выполнить
  • Then - обычно предшествует инструкциям, отвечающим за верификации

Теперь, добавим реализацию данных шагов. В подкаталоге features/step_definitions создадим файл sample.rb и впишем в него следующее:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }require \'test/unit/assertions\'

World(Test::Unit::Assertions)

Given "Login into the system" do
app.login
end

When "Click on \"Search\" button" do
app.click_button "Search"
end

Then "Search screen is displayed" do
assert app.search_screen_displayed? , "No search screen available"
end{/syntaxhighlighter}

Всё, пример готов. Теперь запустим. Cucumber-тесты запускаются из командной строки вида:

{syntaxhighlighter brush: bash;fontsize: 100; first-line: 1; }cucumber <features_folder>{/syntaxhighlighter}

 

Перейдем в каталог, в котором находится каталог </b>features</b> с нашими определениями тестов и запустим командную строку вида:

{syntaxhighlighter brush: bash;fontsize: 100; first-line: 1; }cucumber features{/syntaxhighlighter}

Пойдет выполнение тестов и все выполненные шаги будут по умолчанию выводиться в консоль (различные виды логгирования в Cucumber стоит рассмотреть отдельно). Примерно так это и работает. Ну, и добавим немного "гибкости" в наш код. Как видно из примера, вызов

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }app.click_button "Search"{/syntaxhighlighter}

по дизайну расчитан на то, что нужная кнопка будет находиться по определенному тексту. То есть если нам надо будет нажать на кнопку, например, "Select", то это будет реализовано вызовом

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }app.click_button "Select"{/syntaxhighlighter}

Если же мы работаем с Cucumber, то для обеспечения подобной гибкости, нужно, чтобы Given-When-Then определения могли принимать параметры. И такая возможность есть. На самом деле Ruby-реализации Given-When-Then принимают не строку, а регулярное выражение - некоторый шаблон по которому текст в feature-файле сопоставляется с соответствующей реализацией. Соответственно, мы можем использовать не четкое совпадение строк, а совпадение по шаблону. Например, вместо "Click on \"Search\" link" можно использовать /Click on "(.*)" link/.

Что нам это даст. Во-первых, данный шаблон позволит использовать один шаблон для различных вариаций клика (когда варьируется только название кнопки). Во-вторых, Cucumber использует "обратные ссылки", которые передаются Given-When-Then реализациям на Ruby в качестве параметров. То есть рассматриваемая нами реализация может быть переписана в виде:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }require \'test/unit/assertions\'

World(Test::Unit::Assertions)

Given "Login into the system" do
app.login
end

When /Click on "(.*)" button/ do |button|
app.click_button button
end

Then "Search screen is displayed" do
assert app.search_screen_displayed? , "No search screen available"
end{/syntaxhighlighter}

Измененный код подсвечен жирным шрифтом.

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