Тестирование задачи рейка в rspec (и огурце)

Я новичок в Ruby, и я пытался изучить Rake, RSpec и Cucumber. Я нашел код, который поможет мне проверить мои задачи Rake, но у меня возникли проблемы с его работой. Мне сказали: http://blog.codahale.com/2007/12/20/rake-vs-rspec-fight/, чтобы отказаться от этого:

def describe_rake_task(task_name, filename, &block)
  require "rake"

  describe "Rake task #{task_name}" do
    attr_reader :task

    before(:all) do
      @rake = Rake::Application.new
      Rake.application = @rake
      load filename
      @task = Rake::Task[task_name]
    end

    after(:all) do
      Rake.application = nil
    end

    def invoke!
      for action in task.instance_eval { @actions }
        instance_eval(&action)
      end
    end

    instance_eval(&block)
  end
end

в файл spec_helper.rb.

Мне удалось вывести этот код и запустить его в моих шагах огурца, как это:

When /^I run the update_installers task$/ do
 @rake = Rake::Application.new
 Rake.application = @rake
 load "lib/tasks/rakefile.rb"
 @task = Rake::Task["update_installers"]

 for action in @task.instance_eval { @actions }
  instance_eval(&action)
 end

 instance_eval(&block)

 Rake.application = nil
end

но когда я пытаюсь заставить вещи работать в rspec, я получаю следующую ошибку.

АргументError в 'Задача рейка install_grapevine должен установить каталог mygrapevine '

неправильное количество аргументов (1 для 2) /spec/spec _helper.rb: 21: in instance_eval' /spec/spec_helper.rb: 21:in block in invoke! ' /spec/spec _helper.rb: 20: in each' /spec/spec_helper.rb: 20:in invoke! ' /spec/tasks/rakefile _spec.rb:12:in `block (2 уровня) в

К сожалению, у меня чуть меньше недели рубина под ремнем, так что метапрограммирование вещей над моей головой. Может ли кто-нибудь указать мне в правильном направлении?

Ответ 1

Это работает для меня: (Rails3/Ruby 1.9.2)

When /^the system does it automated tasks$/ do    
  require "rake"
  @rake = Rake::Application.new
  Rake.application = @rake
  Rake.application.rake_require "tasks/cron"
  Rake::Task.define_task(:environment)
  @rake['cron'].invoke   
end

Замените имя своей функции rake здесь, а также обратите внимание, что ваш запрос может быть "lib/tasks/cron", если у вас нет папки lib в вашем пути загрузки.

Я согласен с тем, что вы должны выполнять минимальную работу в задаче Rake и подталкивать остальные модели к простоте тестирования. При этом я считаю важным следить за тем, чтобы код выполнялся в моих задачах cron во время моих тестов интеграции, поэтому я считаю, что очень мягкое тестирование задач рейка оправдано.

Ответ 2

Поскольку тестирование rake для меня слишком много, я стараюсь переместить эту проблему. Всякий раз, когда я нахожусь с длинной задачей рейка, которую хочу протестировать, я создаю модуль/класс в lib/ и перемещаю весь код из задачи там. Это оставляет задачу одной строкой кода Ruby, которая делегирует что-то более проверяемое (класс, модуль, вы называете его). Единственное, что остается непроверенным, заключается в том, вызывает ли команда rake правильную строку кода (и передает правильные параметры), но я думаю, что все в порядке.

Возможно, нам будет полезно рассказать нам, что является 21-й строкой вашего spec_helper.rb. Но учитывая, что подход, который вы опубликовали, углубляется в грабли (ссылаясь на его переменные экземпляра), я бы полностью отказался от него за то, что я предложил в предыдущем абзаце.

Ответ 3

Я только что потратил немного времени на получение огурца, чтобы выполнить задачу грабли, поэтому я решил поделиться своим подходом. Примечание. Это использование Ruby 2.0.0 и Rake 10.0.4, но я не думаю, что поведение изменилось с предыдущих версий.

Есть две части. Первое легко: с правильно настроенным экземпляром Rake::Application, тогда мы можем получить доступ к задачам на нем, вызвав #[] (например, rake['data:import']). Как только у нас есть задача, мы можем запустить ее, вызвав #invoke и передав аргументы (например, rake['data:import'].invoke('path/to/my/file.csv').

Вторая часть более неудобна: правильная настройка экземпляра Rake::Application для работы. Как только мы закончили require 'rake', у нас есть доступ к модулю Rake. У него уже есть экземпляр приложения, доступный от Rake.application, но он еще не настроен - он не знает ни одной из наших задач rake. Однако он знает, где найти наш Rakefile, предположив, что мы использовали одно из стандартных имен файлов: rakefile, rakefile, rakefile.rb или rakefile.rb.

Чтобы загрузить rakefile, нам просто нужно вызвать #load_rakefile в приложении, но прежде чем мы сможем это сделать, нам нужно вызвать #handle_options. Вызов #handle_options заполняет значение options.rakelib значением по умолчанию. Если options.rakelib не задано, метод #load_rakefile взорвется, поскольку он ожидает, что options.rakelib будет перечислимым.

Вот помощник, с которым я столкнулся:

module RakeHelper
  def run_rake_task(task_name, *args)
    rake_application[task_name].invoke(*args)
  end

  def rake_application
    require 'rake'
    @rake_application ||= Rake.application.tap do |app|
      app.handle_options
      app.load_rakefile
    end
  end
end

World(RakeHelper)

Поместите этот код в файл в features/support/, а затем просто используйте run_rake_task в своих шагах, например:

When /^I import data from a CSV$/ do
  run_rake_task 'data:import', 'path/to/my/file.csv'
end

Ответ 4

Поведение могло измениться после того, как был отправлен правильный ответ. У меня возникли проблемы с выполнением двух сценариев, необходимых для выполнения одной и той же задачи rake (только один из них выполнялся, несмотря на меня, используя .execute вместо .invoke). Я решил поделиться своим подходом к решению проблемы (Rails 4.2.5 и Ruby 2.3.0).

Я пометил все сценарии, требующие грабли с помощью @rake, и я определил крючок для установки рейка только один раз.

# hooks.rb
Before('@rake') do |scenario|
  unless $rake
    require 'rake'
    Rake.application.rake_require "tasks/daily_digest"
    # and require other tasks
    Rake::Task.define_task(:environment)
    $rake = Rake::Task
  end
end

(Здесь используется глобальная переменная: https://github.com/cucumber/cucumber/wiki/Hooks#running-a-before-hook-only-once)

В определении шага я просто называю $rake

# step definition
Then(/^the daily digest task is run$/) do
  $rake['collector:daily_digest'].execute
end

Любая обратная связь приветствуется.