Тестирование Rake в Rails: множественная ошибка повышает тишину при тестировании

У меня есть задача rake, которая защищает от опасных граней Rails, основанных на среде. Он работает нормально. Когда я тестирую каждый индивидуальный опасный метод в RSpec, тест проходит. Когда я тестирую несколько строк подряд, для нескольких сред тест выходит из строя после первого. Даже если я запускаю тест несколько раз за одно и то же опасное действие, например, rake db:setup, он будет проходить только в первый раз. Если я запускаю тесты как отдельные операторы it, по одному для каждого опасного действия, пройдут только первые два (есть 4).

Как я могу заставить RSpec вести себя корректно здесь и передать все тесты при запуске в пакете?

Задача грабли

# guard_dangerous_tasks.rake
class InvalidTaskError < StandardError; end
task :guard_dangerous_tasks => :environment do
  unless Rails.env == 'development'
    raise InvalidTaskError
  end
end

%w[ db:setup db:reset ].each do |task|
  Rake::Task[task].enhance ['guard_dangerous_tasks']
end

Тест RSpec

require 'spec_helper'
require 'rake'
load 'Rakefile'

describe 'dangerous_tasks' do
  context 'given a production environment' do
    it 'prevents dangerous tasks' do
      allow(Rails).to receive(:env).and_return('production')

      %w[ db:setup db:reset ].each do |task_name|
        expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
      end
    end
  end

  context 'given a test environment' do
    it 'prevents dangerous tasks' do
      allow(Rails).to receive(:env).and_return('test')

      %w[ db:setup db:reset ].each do |task_name|
        expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
      end
    end
  end
end

Выход RSpec

# we know the guard task did its job,
# because the rake task didn't actually run.
Failure/Error: expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
   expected InvalidTaskError but nothing was raised

Ответы

Ответ 1

Я могу подумать о двух решениях вашей проблемы.

Но сначала нам нужно выяснить, где корень проблемы.

Корень проблемы

Начните с строки из вашего кода:

Rake::Task[task].enhance ['guard_dangerous_tasks']

Сравнивая его с исходным кодом Rake::Task

# File rake/task.rb, line 96
def enhance(deps=nil, &block)
  @prerequisites |= deps if deps
  @actions << block if block_given?
  self
end

вы можете видеть, что guard_dangerous_tasks следует добавить в массив @prerequisites. Его можно легко проверить:

p Rake::Task['db:reset'].prerequisites # => ["environment", "load_config", "guard_dangerous_tasks"]

Продолжая с вами исходный код.

Вы используете invoke для выполнения задач. Если мы уделяем пристальное внимание документации invoke ', в ней говорится:

Вызвать задачу, если она нужна.

Как только задача будет выполнена, ее нельзя было бы вызвать снова (если мы не будем ее повторно использовать).

Но почему это должно быть проблемой? У нас разные задачи, не так ли? Но на самом деле мы этого не делаем!

Мы запускаем guard_dangerous_tasks перед всеми задачами в нашем массиве задач! И он выполняется только один раз.

Решение № 1 не самое лучшее

Как только мы узнаем, где наша проблема, мы можем думать об одном (не лучшее решение).

После каждой итерации повторите функцию guard_dangerous_tasks:

dangerous_task = Rake::Task['guard_dangerous_tasks']
%w[ db:setup db:reset ].each do |task_name|
  expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
  dangerous_task.reenable
end

Решение №2 guard_dangerous_tasks не является обязательным условием

Мы получаем лучшее решение нашей проблемы, если осознаем, что guard_dangerous_tasks не должно быть обязательным условием! Предпосылки должны быть подготовлены и исполняться только один раз. Но мы никогда не должны ослеплять наши глаза от опасностей!

Вот почему мы должны расширять с помощью guard_dangerous_tasks как действие, которое будет выполняться каждый раз при выполнении родительской задачи.

Согласно исходному коду Rake::Task (см. выше), мы должны передать нашу логику в блоке, если хотим, чтобы он был добавлен как действие.

%w[ db:setup db:reset ].each do |task|
  Rake::Task[task].enhance do
    Rake::Task['guard_dangerous_tasks'].execute
  end
end

Мы можем оставить наш тест без изменений и передать:

%w[ db:setup db:reset ].each do |task_name|
  expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
end

Но оставить invoke - это билет на новые проблемы. Лучше заменить на execute:

%w[ db:setup db:reset ].each do |task_name|
  expect { Rake::Task[task_name].execute }.to raise_error(InvalidTaskError)
end

Будьте осторожны с invoke!

Мы сказали выше, что использование invoke - это билет для новых проблем. Какие проблемы?

Попробуйте проверить наш код для сред test и production. Если мы завершим наши тесты в этом цикле:

['production','test'].each do |env_name|
  env = ActiveSupport::StringInquirer.new(env_name)
  allow(Rails).to receive(:env).and_return(env)

  %w[ db:setup db:reset ].each do |task_name|
    expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
  end
end

наш тест завершится неудачно с оригинальной причиной. Вы можете легко исправить это, заменив строку

expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)

с

expect { Rake::Task[task_name].execute }.to raise_error(InvalidTaskError)

Так в чем же причина? Вероятно, вы уже догадались.

В неудачном тесте мы дважды вызывали те же две задачи. В первый раз они были казнены. Во второй раз они должны быть повторно подключены перед выполнением вызова. Когда мы используем execute, действие автоматически включается.

Примечание Здесь вы можете найти рабочий пример этого проекта: https://github.com/dimakura/stackoverflow-projects/tree/master/31821220-testing-rake

Ответ 2

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

class InvalidTaskError < StandardError; end
%w[ db:setup db:reset ].each do |task|
  Rake::Task[task].enhance do
    unless Rails.env == 'development'
      raise InvalidTaskError
    end
  end
end

и в файле spec, следующая модификация создаст два примера, чтобы правильно отслеживать спецификации.

# require 'rails_helper'
require 'spec_helper'
require 'rake'
load 'Rakefile'

describe 'dangerous_tasks' do
  context 'given a production environment' do
    %w[ db:setup db:reset ].each do |task_name|
      it "prevents dangerous tasks #{task_name}" do
        allow(Rails).to receive(:env).and_return('production')
        expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
      end
    end
  end
end

Ответ 3

Пробовал ли вы пропустить конкретную ошибку?:

expect { Rake::Task[task_name].invoke }.to raise_error(StandardError)