Тестовый движок Cucumber (Ruby)
@ Вт, 13 октября 2009, 17:15Зачастую решение по автоматизированному тестированию становится достаточно сложным, что для написания тестов и всего вспомогательного кода требуются навыки программирования, причем иногда даже не начального уровня, а немного повыше. В то же время, имеет место тенденция к упрощению работы с автотестами. Это достигается путем добавления различных визардов, созданию определенных подходов и движков, реализующих данный подход. В частности популярен т.н. keyword-driven подход, когда тесты представляют собой последовательность ключевых слов, структурированную в таблицах. Но есть еще более заковыристый подход, при котором тесты представляются в виде обычных инструкций, написанных в виде обычного текста, для которых есть определенная реализация на уровне программного кода. Для Ruby есть такой движок, именуемый Cucumber.
Итак, допустим, у нас есть некоторый тестовый сценарий, описанный в виде:
- Login into the system
- Click on "Search" button
- Search screen is displayed
Сразу стоит отметить, что сценарий взят с потолка. Мы рассматриваем некоторое "сферическое приложение в вакууме", у которого есть функциональность для залогинивания, а также есть некоторый функционал поиска.
Допустим, на Ruby этот тест реализуется в виде:
app.login
app.click_button "Search"
assert app.search_screen_displayed? , "No search screen available"
Исходим из предположения, что весь необходимый для выполнения данного сценария функционал у нас есть. Вот так вот компактно можно реализовать данный тест. Но это просто и легко для тех, кто программирует на Ruby. А теперь представим, что у нас есть тест-дизайнер, который просто пишет сценарии. Как сделать так, чтобы ему можно было создавать тесты, которые можно было бы запускать автоматически? Вот подобными вещами Cucumber и занимается.
Что это такое? По сути - это текстовый парсер, который оперирует определенными текстовыми конструкциями и определенному тексту ставит в соответствие некоторый блок программного кода на Ruby. Этот модуль предоставляется как отдельный gem. Чтобы его установить надо выполнить командную строку:
gem install cucumber
Итак, исходя из определения, у нас есть обычный текст теста и поставленные в соответствие определенным фразам блоки кода. То есть имеет место разделение описания и реализации. В Cucumber это выражается в том, что описание тестов хранится в файле с расширением *.feature, а сама реализация находится в файле с расширением *.rb. Более того, файлы формируются исходя из определенной структуры каталогов. Вообще, Cucumber работает со структурой сформированной по следующим правилам:
- Главный каталог, содаржащий описание тестов и их реализацию именуется features именнов этом каталоге и во всех вложенных каталогах могут находиться feature-файлы
- Все Ruby-реализации инструкций находятся в подкаталоге features/step_definitions и во всех вложенных подкаталогах
- Вспомогательный функционал находится в подкаталоге features/support
- В подкаталоге features/support находится файл env.rb, который отвечает за функционал, выполняемый до/после тестов, какие-то настройки и т.п. Это отдельная тема.
Итак, определившись со структурой, можем создать определение нашего теста. В каталоге features создадим файл sample.feature и напишем определение теста вот так:
Feature: Cucumber sample testing
Scenario: Sample scenario
Given Login into the system
When Click on "Search" button
Then Search screen is displayed
Вот пример описания теста в Cucumber. Здесь жирным шрифтом отмечены 5 ключевых слов (это не все ключевые слова, но наиболее часто используемые):
- Feature: - просто содержит описание группы тестов
- Scenario: - предшествует имени теста
- Given - обозначает, что инструкция за этим словом - это некоторые предварительные настройки или начальное состояние
- When - обозначает, что инструкция за этим словом - это некоторые некоторое действие, которое надо выполнить
- Then - обычно предшествует инструкциям, отвечающим за верификации
Теперь, добавим реализацию данных шагов. В подкаталоге features/step_definitions создадим файл sample.rb и впишем в него следующее:
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
Всё, пример готов. Теперь запустим. Cucumber-тесты запускаются из командной строки вида:
cucumber <features_folder>
Перейдем в каталог, в котором находится каталог </b>features</b> с нашими определениями тестов и запустим командную строку вида:
cucumber features
Пойдет выполнение тестов и все выполненные шаги будут по умолчанию выводиться в консоль (различные виды логгирования в Cucumber стоит рассмотреть отдельно). Примерно так это и работает.
Ну, и добавим немного "гибкости" в наш код. Как видно из примера, вызов
app.click_button "Search"
по дизайну расчитан на то, что нужная кнопка будет находиться по определенному тексту. То есть если нам надо будет нажать на кнопку, например, "Select", то это будет реализовано вызовом
app.click_button "Select"
Если же мы работаем с Cucumber, то для обеспечения подобной гибкости, нужно, чтобы Given-When-Then определения могли принимать параметры. И такая возможность есть. На самом деле Ruby-реализации Given-When-Then принимают не строку, а регулярное выражение - некоторый шаблон по которому текст в feature-файле сопоставляется с соответствующей реализацией. Соответственно, мы можем использовать не четкое совпадение строк, а совпадение по шаблону. Например, вместо "Click on \\"Search\\" link" можно использовать /Click on "(.*)" link/.
Что нам это даст. Во-первых, данный шаблон позволит использовать один шаблон для различных вариаций клика (когда варьируется только название кнопки). Во-вторых, Cucumber использует "обратные ссылки", которые передаются Given-When-Then реализациям на Ruby в качестве параметров. То есть рассматриваемая нами реализация может быть переписана в виде:
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
Измененный код подсвечен жирным шрифтом.
Это только описание базовых возможностей. На самом деле данный движок достаточно обширный и содержит ряд "фишек", которые могут позволить создавать тесты даже тем, кто не имеет нужных навыком программирования. Главное обеспечить нужное количество реализованных инструкций, чтобы потом собирать тесты "по кирпичикам".
