(видимо) одинаковые тесты для двух рейк-задач; только один проходит

Я пытаюсь написать тесты в 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

spec/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 spec/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)>'

person gregates    schedule 08.09.2012    source источник


Ответы (2)


Кратко: измените 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 дважды? Ваша цель — протестировать свои задачи, что не требует нового контекста рейка для каждой спецификации.

Вы можете полностью исключить local за счет некоторой незначительной многословности в каждой спецификации:

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

ИМО, менее желательно по ряду причин. Вот резюме, с которым я согласен.

person Dave Newton    schedule 09.09.2012
comment
Это делает оба теста пройденными. Но можете ли вы объяснить, почему? - person gregates; 09.09.2012
comment
@gregates Не совсем без дополнительных копаний. Я предполагаю, что второй rake_require не загружает файл, потому что он уже был загружен один раз, поэтому новые определения задач исчезли для второго теста. - person Dave Newton; 09.09.2012
comment
Прохладный. Что ж, спасибо за помощь. Еще хотелось бы разобраться в этом явлении получше. - person gregates; 09.09.2012
comment
По непонятным мне причинам, если вы поместите require etc. в before :all и дважды вызовете одну и ту же задачу, второй вызов не произойдет. К счастью, другое предложение работает: оставьте require в before :each и используйте [] в качестве третьего параметра. Тогда работает второй вызов. - person Mark Berry; 13.03.2015
comment
Не пробовал с вашим примером, но, возможно, использование execute вместо invoke заставит все ваши задачи выполняться. invoke запускает задачу только в том случае, если она считает это необходимым (не не спрашивайте меня об используемых критериях) - person Waiting for Dev...; 02.12.2015

Популярная поисковая система привела меня сюда, так как в моем случае я видел, как тесты терпели неудачу, когда #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 только когда-либо запускает задачу один раз. Состояние «выполнялось до», по-видимому, запоминается внутри экземпляра Rake.application.

Да, это означает, что, по крайней мере, в Rake v12, как было протестировано, множество онлайн-статей, показывающих, как тестировать задачи Rake, (fo) неверны или им это сойдет с рук, потому что они показывают только один тест. для любой заданной задачи в своих примерах.

Мы могли бы использовать #execute Rake, но он не запускает зависимые задачи, поэтому создает свой собственный набор проблем и еще больше отдаляет нас от тестирования стека 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.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 не вели себя таким образом, поэтому эти статьи были правильными на момент их написания.

Надеюсь, кто-то найдет это полезным.

Справочные статьи:

(подсказка поисковой системы: тестирование рейка rspec test invoke вызывается только один раз)

person Andrew Hodgkinson    schedule 18.09.2019