在rspec(和黄瓜)中测试rake任务

时间:2010-04-10 13:27:04

标签: ruby rspec cucumber metaprogramming rake

我是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中运行时,我收到以下错误。

  

'Rake任务中的ArgumentError   install_grapevine应安装到   mygrapevine目录'

     

错误的参数数量(1对2)   /spec/spec_helper.rb:21:在instance_eval' /spec/spec_helper.rb: 21:in块中调用!'   /spec/spec_helper.rb:20:in each' /spec/spec_helper.rb: 20:in调用!'   /spec/tasks/rakefile_spec.rb:12:in块(2级)in   “

不幸的是,我带着一个星期的红宝石,所以元编程的东西已经超出我的想象。有人能指出我正确的方向吗?

4 个答案:

答案 0 :(得分:19)

这对我有用:(Rails3 / Ruby 1.9.2)

When /^the system does it's 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文件夹,请注意您的require可能是“lib / tasks / cron”。

我同意你应该只在Rake任务中做最少的工作,并将其余部分推送到模型以便于测试。话虽如此,我认为在集成测试期间确保代码在我的cron任务中实际运行非常重要,所以我认为对rake任务进行非常温和的测试是合理的。

答案 1 :(得分:16)

由于测试rake对我来说太过分了,我倾向于解决这个问题。每当我发现自己需要测试一个漫长的rake任务时,我会在lib/中创建一个模块/类,并从那里移动任务中的所有代码。这将任务留给了一行Ruby代码,它代表了一些更可测试的东西(类,模块,你可以命名)。唯一未经测试的是rake任务是否调用正确的代码行(并传递正确的参数),但我认为没问题。

告诉我们spec_helper.rb的第21行可能有用。但是考虑到你发布的方法深入挖掘rake(指的是它的实例变量),我会完全放弃它,因为我在前一段中的建议。

答案 2 :(得分:5)

我花了一点时间让黄瓜去执行一个rake任务,所以我想我会分享我的方法。注意:这是使用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,假设我们使用了一个标准文件名:rakefileRakefilerakefile.rbRakefile.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

答案 3 :(得分:3)

自发布正确答案后,行为可能已更改。我在执行两个需要运行相同rake任务的场景时遇到问题(尽管我使用.execute而不是.invoke,但只执行了一个)。我想分享我解决问题的方法(Rails 4.2.5和Ruby 2.3.0)。

我用@rake标记了所有需要rake的场景,并且我定义了一个钩子来设置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

欢迎任何反馈。