Domanda Test di Rake in Rails: più errori generano test di silenziamento


Ho un compito di rastrello che protegge dai pericolosi Rails rake rasks, in base all'ambiente. Funziona bene. Quando provo ogni singolo metodo pericoloso in RSpec, il test passa. Quando provo più di una fila, per più ambienti, il test fallisce dopo il primo. Anche se eseguo il test più volte per la stessa azione pericolosa, rake db:setup per esempio, passerà solo la prima volta. Se eseguo i test come singoli it dichiarazioni, una per ogni azione pericolosa, passeranno solo i primi due (ce ne sono 4).

Come posso far funzionare correttamente RSpec qui e passare tutti i test quando viene eseguito in una suite?

Il compito del rake

# 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

Il test di 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

Uscita 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

18
2017-08-04 23:50


origine


risposte:


Posso pensare a due soluzioni del tuo problema.

Ma prima dobbiamo scoprire dove è la radice del problema.

Radice del problema

Iniziamo con una riga del tuo codice:

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

Confrontandolo con il codice sorgente di Rake::Task

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

potete vederlo guard_dangerous_tasks dovrebbe essere aggiunto a @prerequisites array. Può essere facilmente controllato:

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

Continuando con il tuo codice sorgente.

Usate invoke per eseguire attività. Se prestiamo molta attenzione a invoke's' documentazione, si afferma:

Richiama l'attività, se lo è necessaria.

Una volta eseguita l'attività, non è possibile richiamarla di nuovo (a meno che non la riattiviamo).

Ma perché questo dovrebbe essere un problema? Stiamo svolgendo diversi compiti, no? Ma in realtà non lo facciamo!

Corriamo guard_dangerous_tasks prima di tutte le attività nel nostro array di compiti! Ed è stato eseguito solo una volta.

La soluzione n. 1 non è la migliore

Non appena sappiamo dove è il nostro problema, possiamo pensarci su uno (non la soluzione migliore).

Riattiviamo guard_dangerous_tasks dopo ogni iterazione:

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

Soluzione n. 2 guard_dangerous_tasks non è un prerequisito

Otteniamo una soluzione migliore del nostro problema se ce ne rendiamo conto guard_dangerous_tasks non dovrebbe essere un prerequisito! I prerequisiti devono "prepararsi" e devono essere eseguiti una sola volta. Ma non dovremmo mai accecare i nostri occhi ai pericoli!

Questo è il motivo per cui dovremmo estendere guard_dangerous_tasks come azione, che verrà eseguita ogni volta che viene eseguita l'attività principale.

Secondo il codice sorgente di Rake::Task (vedi sopra) dovremmo passare la nostra logica in un blocco se vogliamo che venga aggiunta come azione.

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

Possiamo lasciare il nostro test invariato ora e passa:

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

Ma andiamo via invoke è un biglietto per nuovi problemi. È meglio essere sostituito con execute:

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

Stai attento invoke!

Abbiamo detto sopra, che usando invoke è un biglietto per nuovi problemi. Che tipo di problemi?

Proviamo a testare il nostro codice per entrambi test e production ambienti. Se avvolgiamo i nostri test all'interno di questo ciclo:

['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

il nostro test fallirà con la ragione originale. Puoi sistemarlo facilmente sostituendo la linea

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

con

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

Quindi qual è stata la ragione? Probabilmente lo indovini già.

Nel test fallito abbiamo invocato due volte le stesse due attività. La prima volta sono stati giustiziati. La seconda volta dovrebbero essere riabilitati prima di eseguire il richiamo. Quando usiamo execute, l'azione è riattivabile automaticamente.

Nota Puoi trovare l'esempio di lavoro di questo progetto qui: https://github.com/dimakura/stackoverflow-projects/tree/master/31821220-testing-rake


7
2017-09-04 15:36



Sembra che due attività non possano fare riferimento allo stesso compito per enhancement quindi forse c'è un conflitto in fase di esecuzione. Quindi prova il block metodo per gestire la situazione.

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

e nel file spec, la seguente modifica creerebbe due esempi per tracciare correttamente le specifiche.

# 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

1
2017-09-01 16:44



Hai provato a passare l'errore specifico ?:

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

0
2017-08-05 00:49