Ruby: Cucumber + Rake + немного полезностей
@ Вт, 27 октября 2009, 13:04Cucumber - это просто тестовый движок на Ruby, который позволяет создавать тесты, используя "human language" инструкции. Но это один из аспектов. Но есть и другой аспект - это такая же вспомогательная утилита на Ruby, как и многие другие. И как многие другие Ruby-компоненты, Cucumber вписывается в определенную инфраструктуру. В частности, можно определить некоторые специфические задачи, которые он может выполнять. Помимо этого, иногда полезно получить информацию о том, собирается ли набор тестов без ошибок, какие тесты относятся к той или иной группе, какие шаги еще не реализованы и т.п. Наличие подобных "фишек" помогает во многом.
Во-первых, было бы неплохо завернуть отдельные специфические задачи в более компактный вид, чем стандартная командная строка Cucumber-а. В частности, если в стандартной командной строке для запуска определенной группы тестов надо указывать помимо рабочего каталога, еще и теги, то было бы неплохо иметь в наличии команду вида:
some_command [task_name [parameters]]
Для подобных целей существует такой модуль Ruby как Rake. Для его установки достаточно выполнить команду:
gem install rake
Это некий аналог Make-утилиты для C++, ANT для Java и т.п. Этот модуль использует файлы определенного вида, у котором определены задачи, которые надо выполнить. Более того, различные Ruby-модули имеют свой зарезервированный класс, позволяющий определять, с какими опциями Rake будет запускать данный модуль. Cucumber тут не исключение. У него есть класс Cucumber::Rake::Task, который запускает тесты на выполнение. Нам остается определить только опции, с которыми будет произведен данный запуск.
Итак, допустим у нас есть некоторый рабочий каталог, назовем его working_dir, в котором находится каталог features, содержащий описания и реализации тестов. В каталоге working_dir создадим файл Rakefile (это одно из стандартных имен, которое Rake воспринимает по умолчанию) и впишем в него следующее:
require "cucumber/rake/task"
Cucumber::Rake::Task.new(:run) do |task|
task.cucumber_opts = ["features"]
end
Всё, мы создали задачу, которая выполнит все тесты в папке features. Зайдем в каталог working_dir и запустим командную строку вида:
rake run
Данная строка выполнит Rake-задачу, определенную нами в Rakefile файле под именем run и выполнит запуск Cucumber-а с указанными опциями. В данном случае вышеприведенная командная строка соответствует командной строке Cucumber-а вида:
cucumber features
Пока упрощение заметно не сильно, но мы можем добавить еще опций. Например, у нас все тесты отмечены определенными тегами, соответствующими некоторым под-группам тестов. И у нас есть некий общий тег, который присутствует у всех тестов, которые мы так или иначе запускаем. Допустим, этот общий тег называется @all. Тогда командная строка для запуска Cucumber-тестов с данным тегом имеет вид:
cucumber -t @all features
А теперь обновим наш Rakefile файл вот таким образом:
require "cucumber/rake/task"
Cucumber::Rake::Task.new(:run) do |task|
task.cucumber_opts = ["-t","@#{ENV["TAG"] || "all" }","features"]
end
Жирным подсвечены изменения. Мы добавили 2 опции: опцию тега и непосредственно определение имени используемого тега. При этом запуск всех тестов по-прежнему будет осуществляться строкой:
rake run
Но если нам допустим надо запустить тесты с некоторым тегом, отличным от @all, например, @test, то нам остается только набрать строку вида:
rake run tag=test
И это будет равносильно строке вида
cucumber -t @test features
Это только начало. Теперь давайте посмотрим, как сделать проверку на то, что все тесты в папке features пригодны для выполнения. В основном могут возникнуть ошибки из-за того, что те или иные ключевые слова могут быть определены дважды или же одно ключевое слово одинаково подпадает под несколько регулярных выражений в step definitions. На самом деле при запуске тестов Cucumber подхватывает все features, которые определены в указанном каталоге и предварительно обрабатывает всё, но запускает только ту часть тестов, которая задана указанными тегами. Соответственно, нам достаточно создать определенный feature-файл, в котором будет всего одна инструкция, которая просто ничего не делает. Если она выполниться, то это означает, что весь набор тестов пригоден для запуска.
Итак, в каталоге working_dir/features создадим файл compile.feature и заполним его содержимым вида:
@compile
Feature: Compilation tests
Scenario: Compile Test
Given Build is OK
А таперь в working_dir/features/step_definitions создадим файл compile.rb, в котором будет только следующее:
Given /Build is OK/ do
end
Всё. Теперь мы можем перейти в working_dir каталог и запустить командную строку вида:
rake run tag=compile
И если не будет никаких ошибок, то всё отлично. Поскольку данная задача достаточно специфична и при этом используется часто, особенно при разработке новых тестов, то имеет смысл вынести ее в отдельную Rake-задачу. Обновим наш Rakefile-файл следующим образом:
require "cucumber/rake/task"
Cucumber::Rake::Task.new(:run) do |task|
task.cucumber_opts = ["-t","@#{ENV["TAG"] || "all" }","features"]
end
Cucumber::Rake::Task.new(:compile) do |task|
task.cucumber_opts = ["-t","@compile","features"]
end
После этих изменений (подсвечено жирным шрифтом) проверку на компиляцию мы можем вызывать строкой вида:
rake compile
Уже заметно проще.
Теперь, еще полезной фичей будет возможность вывода на экран списка тестов, заданных некоторым тегом, а также вывод списка шагов, которые не имеют реализаций в step definitions.
Для этого, нам надо создать свой класс, выполняющий вывод результатов. Для начала сориентируемся, что используется версия Cucumber-a 0.3.101, в более поздних версиях класс, ответственный за форматированный вывод претерпел изменение и код в примерах ниже работать не будет.
Сразу отметим, что все пользовательские классы для форматного вывода результатов должны настедоваться от Cucumber::Ast::Visitor класса. Отнаследованный класс должен содержать определения основных методов, которые срабатывают при начале обработки feature-файла, при начале и завершении выполнения шагов и т.п. В нашем случае надо переопределить всего 4 метода:
- initialize
- visit_feature
- visit_feature_element
- visit_step_result
В каталоге working_dir/features/support создадим файл print_steps и реализуем класс вида:
class PrintSteps < Cucumber::Ast::Visitor
def initialize(step_mother, io, options)
super(step_mother)
end
def visit_feature(feature)
puts feature.name
super
end
def visit_feature_element(feature_element)
puts "\\\\t#{feature_element.name}"
super
end
def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
puts "\\\\t\\\\t#{step_match.name}" if status == :undefined
end
end
После этого, мы можем перейти в каталог working_dir и выполнить командную строку вида:
cucumber -d -f PrintSteps features
после чего в консоль выведется список features, сценариев и имена шагов, которые не имеют Ruby-реализации. Причем, имена фич будут идти без отступа, имена сценариев с отступов в 1 табуляцию, а нереализованные шаги - с 2-мя таболяциями.
Подобная вещь полезна при разработке, чтоб следить за объемом тестов и насколько они реализованы, а также проследить, что внесенные изменения в существующие реализации ключевых слов не повредили какие-либо тесты.
Теперь создадим Rake-задачу, которая выполнит данные действия для определенных тегов. В Rakefile-файле добавим строки вида:
require "cucumber/rake/task"
Cucumber::Rake::Task.new(:run) do |task|
task.cucumber_opts = ["-t","@#{ENV["TAG"] || "all" }","features"]
end
Cucumber::Rake::Task.new(:compile) do |task|
task.cucumber_opts = ["-t","@compile","features"]
end
Cucumber::Rake::Task.new(:list) do |task|
task.cucumber_opts = ["-d", "-f", "PrintNames", "-t","@#{ENV["TAG"] || "all" }","features"]
end
После этих изменений, мы можем вывести список всех тестов в группе @all с нереализованными шагами вот такой строкой:
rake list
Если же надо подобный вывод сделать по некоторому специфическому тегу, например, @test, то сделать это можно строкой вида:
rake list tag=test
Вот и всё. У нас есть вспомогательные решения, позволяющие следить за работоспособностью наших тестов, написанных с использованием движка Cucumber. Эти задачи могут быть использованы как локально, так и на сборочном сервере. Например, при каждой операции check-in срабатывает строка rake compile.
