7

我正在尝试在 rspec 中为在同一文件中定义的两个 rake 任务编写测试(在 Rails 3.0.11 项目中)。由于某种原因,其中只有一个通过了。我写了一个小演示来抽象出任务的实际内容,同样的事情也会发生。rake当从命令行调用时,这两个任务都可以工作。这是怎么回事?这是我的演示:

lib/tasks/demo_tasks.rake

namespace :demo do
  task :test => :environment do
    puts "test!"
  end

  task :test_two => :environment do
    puts "second test!"
  end
end

规范/lib/tasks/demo_spec.rb

require 'spec_helper'
require 'rake'

describe "test tasks" do
  let(:rake) do
    app = Rake::Application.new
    app.options.silent = true
    app
  end

  before :each do
    Rake.application = rake
    Rake.application.rake_require 'lib/tasks/demo_tasks',
                                  [Rails.root.to_s]
    Rake::Task.define_task :environment
  end

  describe "demo:test" do
    it "runs" do
      rake["demo:test"].invoke
    end
  end

  describe "demo:test_two" do
    it "also_runs" do
      rake["demo:test_two"].invoke
    end
  end
end

rspec 规范/lib/tasks/demo_spec.rb

test tasks
  demo:test
test!
    runs
  demo:test_two
    also_runs (FAILED - 1)

Failures:

  1) test tasks demo:test_two also_runs
     Failure/Error: rake["demo:test_two"].invoke
     RuntimeError:
       Don't know how to build task 'demo:test_two'
     # ./spec/lib/tasks/demo_spec.rb:26:in `block (3 levels) in <top (required)>'
4

2 回答 2

9

简而言之:将您的更改beforebefore :all(而不是:each)。

或者:将一个空数组作为第三个参数传递给rake_require.

Rake.application.rake_require 'lib/tasks/demo_tasks', 
                              [Rails.root.to_s], 
                              []

细节

def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
  fn = file_name + ".rake"
  return false if loaded.include?(fn)
  ...

$"是一个 Ruby 特殊变量,它保存由require.

如果您不传递可选参数,rake_require将使用 Ruby 加载的模块数组。这意味着模块不会再次加载:Ruby 知道模块已加载,rake 检查 Ruby 知道的内容,并且它是每个测试的新 rake 实例。

切换到before :all工作,因为这意味着该let块只运行一次:一个 rake 实例,一个模块加载,每个人都很高兴。

说了这么多,为什么还要重新加载 rake 环境两次呢?您的目标是测试您的任务,这不需要为每个规范提供新的 rake 上下文。

您可以以每个规范中的一些次要冗长为代价完全消除本地:

describe "test tasks" do
  before :all do
    Rake.application = Rake::Application.new
    Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s]
    Rake::Task.define_task :environment
  end

  describe "demo:test" do
    it "runs" do
      Rake::Task["demo:test"].invoke
    end
  end
end

您可以在块中定义一个实例变量before以避免Rake::Task引用:

before :all do
  @rake = Rake::Application.new
  Rake.application = @rake
  Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s]
  Rake::Task.define_task :environment
end

describe "demo:test" do
  it "runs" do
    @rake["demo:test"].invoke

出于多种原因,IMO 不太理想。这是我同意的总结

于 2012-09-09T00:09:41.017 回答
3

一个流行的搜索引擎把我带到了这里,因为在我的例子中,当#invoke一个给定的测试被多次使用时,我看到测试失败。下面的解决方案建立在@dave-newtown 的回答之上。

出现问题是因为在撰写本文时(Rake v12),#invoke运行一次任务,例如:

RSpec.describe "demo:test" do
  it "runs" do
    expect(SomethingWeAreInvoking).to eql(ProofIfWasInvoked)
    Rake::Task["demo:test"].invoke
  end

  it "runs" do
    expect(SomethingWeAreInvoking).to eql(ProofIfWasInvoked)
    Rake::Task["demo:test"].invoke
  end
end

it...如果测试写得好并且任务正确调用,则可能会通过第一个运行,但总是会在第二个失败,it因为在给定Rake.application的范围内,使用#invoke 只运行一次任务。has-been-run-before 状态显然在Rake.application实例中被记住了。

是的,这确实意味着至少在经过测试的 Rake v12 下,许多显示如何测试 Rake 任务的在线文章是(fo)不正确的,或者侥幸逃脱,因为它们在示例中只显示了对任何给定任务的单一测试.

我们可以使用 Rake's #execute,但这不会运行相关任务,因此会导致其自身的一系列问题,并使我们远离测试 Rake 堆栈,就像在命令行上调用它一样。

取而代之的是,将接受的答案与在线其他零碎的混合产生这种替代方案:

require 'spec_helper'
require 'rake'

RSpec.describe 'demo:test' do
  before :each do
    Rake.application = Rake::Application.new
    Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s], []
    Rake::Task.define_task(:environment)
  end

  it 'runs' do
    expect(SomethingWeAreInvoking).to eql(ProofIfWasInvoked)
    Rake.application.invoke_task('demo.test')
  end

  it 'runs with a parameter' do
    expect(SomethingWeAreInvoking).to eql(ProofIfWasInvoked)
    Rake.application.invoke_task('demo.test[42]')
  end
end
  • 它准备 Rake on before :each
  • 每次Rake.application都会创建一个新的;这意味着我们可以invoke在任意数量的测试中使用,尽管在任何单个测试中只能使用一次。
  • 如果我们只是使用Rake.application开箱即用的实例,我们可以在Rake.application.rake_require 'tasks/demo_tasks'所有路径等都设置好后编写,但是因为我们肯定需要为每个测试创建一个新的 Rake 应用程序实例,以避免在测试中“弄脏”它的状态,需要来自@dave-newtown 的“速记”表格。
  • 我使用Rake.application.invoke_task而不是Rake::Task[...].invoke. 这使参数的语法与 Rake 命令行上使用的语法相同,我觉得这是一种更“准确”和自然的方式来测试需要 args 的任务。

是的,这确实意味着至少在经过测试的 Rake v12 下,许多展示如何测试 Rake 任务的在线文章是不正确的,或者侥幸逃脱,因为它们在示例中只显示了对任何给定任务的单一测试。很可能早期的 Rake 版本没有这种行为,所以这些文章在撰写时是正确的。

希望有人觉得这很有帮助。

参考文章:

(搜索引擎提示:testing rake rspec test invoked invoked only run once)

于 2019-09-18T22:11:17.120 回答