ruby-on-rails 对于两个RAKE任务的(显然)相同测试;仅一次通过

uhry853o  于 2023-02-10  发布在  Ruby
关注(0)|答案(3)|浏览(203)

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

库/任务/演示任务.rake

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

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

规范/库/任务/演示规范.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规范/库/任务/演示规范.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)>'
wgx48brx

wgx48brx1#

    • 简单说明:**将您的before更改为before :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,由于多种原因不太理想。Here's a summary I agree with

pxy2qtax

pxy2qtax2#

一个流行的搜索引擎引导我来到这里,因为在我的案例中,当#invoke在一个给定的测试中被多次使用时,我看到测试失败了。
出现此问题的原因是在编写时(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只运行一次任务。
是的,这确实意味着 * 至少在Rake v12* 下测试,许多在线文章显示如何测试Rake任务是(fo)不正确的,或者侥幸逃脱,因为他们只显示了一个单一的测试,任何给定的任务在他们的例子。
我们可以使用Rake的#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
  • 它在before :each上准备Rake。
  • 每次创建一个新的Rake.application;这意味着我们可以在任何数量的测试中使用invoke,尽管在任何单个测试中仅使用一次。
  • 如果我们只使用Rake.application开箱即用的示例,我们可以在设置所有路径等时只写Rake.application.rake_require 'tasks/demo_tasks',但由于我们肯定需要为每个测试提供一个新的Rake应用程序示例,以避免在测试中“弄脏”它的状态,因此需要来自@dave-newtown的“手写”表单。
  • 我使用Rake.application.invoke_task而不是Rake::Task[...].invoke,这使得参数的语法与Rake命令行上使用的语法相同,我觉得这是一种更“准确”和自然的测试使用参数的任务的方法。

是的,这确实意味着 * 至少在Rake v12* 下测试时,许多在线文章显示如何测试Rake任务是不正确的,或者因为它们只显示了示例中任何给定任务的单个测试而侥幸逃脱。很可能早期的Rake版本没有这样做,所以这些文章在编写时是正确的。
希望有人觉得这有帮助。
参考文献:

  • https://www.rubydoc.info/gems/rake/Rake/Task#invoke-instance_method-线索在“调用任务 * 如果需要 *。先决条件被首先调用”(我强调)
  • 比较https://www.rubydoc.info/gems/rake/Rake/Task#execute-instance_method-“执行与此任务相关的操作”,未提及“如果需要”或先决条件
  • 搜索引擎生成https://medium.com/@sampatbadhe/rake-task-invoke-or-execute-419cd689c3bd等文章

(搜索引擎提示:测试rake rspec测试调用已调用仅运行一次)

8iwquhpp

8iwquhpp3#

Rake跟踪哪些任务已经运行过,并假设您只想运行它们一次。一个简单的解决方案是在调用任务之前,在before(:each)钩子中调用任务上的#reenable。例如,假设Rake任务应该调用SomeClass上的两个方法,并且您想在单独的示例中测试它们:

RSpec.describe Rake::Task['my_task'] do
  before do
    allow(SomeClass).to receive(:method1)
    allow(SomeClass).to receive(:method2)

    described_class.reenable
    described_class.invoke
  end

  it 'does a thing' do
    expect(SomeClass).to have_received(:method1)
  end

  it 'does another thing' do
    expect(SomeClass).to have_received(:method2)
  end
end

相关问题