Rspec-core: Acesso a metadados de opções arbitrárias no bloco "antes"

Criado em 16 jun. 2010  ·  9Comentários  ·  Fonte: rspec/rspec-core

Estou tentando usar o Steak e o Capybara para executar alguns cenários usando um back-end compatível com JavaScript (Culerity) e outros usando o back-end padrão rack_test.

No Cucumber isso foi feito usando tags, mas com Steak e RSpec 1.3 me disseram que você pode fazer algo assim:

scenario 'foo', :js => true do
  # ... this one runs under culerity
end

scenario 'bar' do
  # ... this one runs under rack_test
end

Isso é ativado por esta configuração:

Spec::Runner.configure do |config|    
  config.before :each do
    Capybara.current_driver = :culerity if options[:js]
  end

  config.after :each do
    Capybara.use_default_driver if options[:js]
  end
end

Sob RSpec 2 este truque não funcionará; parece que "opções" não está disponível no escopo desses blocos de configuração antes/depois. Consegui desenterrar as opções por meio de uma inspeção "instance_variable_get" muito feia:

RSpec.configure do |config|
  config.before :each do
    if self.running_example.instance_variable_get(:@options)[:js]
      Capybara.current_driver = :culerity
    end
  end

  config.after :each do
    if self.running_example.instance_variable_get(:@options)[:js]
      Capybara.use_default_driver
    end
  end
end

Portanto, este ticket é para obter acesso aos metadados arbitrários que podem ser passados ​​para os blocos "describe" e "it" por meio do hash de opções.

Observe que no caso de uso que estou falando acima, usar o material "filter_run" não me dará o que eu quero (isso é para selecionar um _subconjunto_ dos exemplos a serem executados, mas eu quero executar _todos_ os exemplos, mas troque os motoristas Capivara na hora).

O que você acha de fornecer acesso às opções por meio de um acessador?

Percebi que posso acessar as opções passadas para a subclasse RSpec::Core::Example; ou seja:

it "should foo", :stuff => true do
  ...
end

Mas não consigo entender as coisas passadas para a subclasse RSpec::Core::ExampleGroup; ou seja:

describe "foo", :thing => true do
  ...
end

Na prática, não me importo muito com o acesso a esses metadados, porque quero alternar os drivers por cenário (ou seja, no nível "it/scenario"). Mas se você concorda em disponibilizar os metadados nesse nível, acho que faz sentido disponibilizar os metadados no nível "descrever/recurso" também.

O que você acha?

Se eu conseguir um "sinal verde" sobre a ideia, ficarei feliz em colocar um remendo.

Saúde,
Wincent

Comentários muito úteis

Apenas para registro, já que esse problema é o que encontrei usando a consulta do mecanismo de pesquisa "rspec metadata antes" e como não consegui encontrá-lo documentado em nenhum lugar.

Esse comportamento pode ser obtido em uma versão rspec atualizada da seguinte maneira:

before do |example|
  example.metadata[:foo] # => true
end

it "should behave", :foo => true do
end

Todos 9 comentários

Luz verde! Eu gostaria que ele funcionasse através da variável running_example, não apenas opções, para manter o namespace local organizado. Qualquer um destes funcionaria para mim:

 RSpec.configure do |config| 
 config.before :cada um faz
 Capybara.current_driver = :culerity if running_example[:js]
 fim
 fim

 RSpec.configure do |config| 
 config.before :cada um faz
 Capybara.current_driver = :culerity if running_example.options[:js]
 fim
 fim

Separado, mas relacionado, o que você acha de renomear running_example para example?

Sim, faz sentido evitar desordenar o namespace local. Olhando as opções:

1: if example[:foo]
2: if example.options[:foo]
3: if running_example[:foo]
4: if running_example.options[:foo]

A opção "2" é mais agradável para mim porque está próxima do inglês "se a opção de exemplo foo estiver definida". Eu vou com isso por enquanto.

Vou começar a jogar e ver se consigo montar um patch antes do fim do dia.

Excelente. Obrigado!

Ok, tenha algo agora. Visto que você não pode anexar patches a tickets do Github, enviei alguns commits para forks aqui:

http://github.com/wincent/rspec-core/commits/core-ticket-42

E aqui:

http://github.com/wincent/rspec-rails/commits/core-ticket-42

Especificamente, estamos falando sobre esses commits:

http://github.com/wincent/rspec-core/commit/99f0428d53b98cee8a60250d1347224f3dbc5697
http://github.com/wincent/rspec-core/commit/2ed401684b68e404d75be051b932d592b260dc00
http://github.com/wincent/rspec-rails/commit/66c5df14c5f7e4d0f8e42f98009837077c680437
http://github.com/wincent/rspec-rails/commit/0c5801a412bfd3abaac5ed7222e4e1d34e4e78e7

As mudanças reais na base de código foram triviais (20 minutos de codificação), mas tive alguns problemas e passei várias horas tentando manter as especificações e recursos passando.

Um dos problemas é que é bastante complicado se você fizer uma alteração que exija modificações simultâneas em diferentes repositórios. Por exemplo, a renomeação de "running_example" para "example" no rspec-core requer alterações no rspec-rails porque o último usa "running_example".

Então, você faz a alteração em ambos os repositórios de uma vez, mas obtém falhas no rspec-rails porque está usando a versão do sistema do rspec-core em vez da versão local.

As falhas de especificação podem ser corrigidas ajustando o Gemfile no aplicativo Rails de exemplo gerado para que ele use a versão local do rspec-core em vez de alguma outra versão que ele possa extrair de outro lugar no sistema. (É isso que o commit 0c5801a412bfd faz.)

Dessa forma, você pode fazer a alteração no rspec-core e no rspec-rails ao mesmo tempo, e todas as especificações continuam passando.

As falhas de recursos são outra questão inteiramente. O Cucumber insiste em usar alguma outra versão do rspec-core em vez do local. Passei horas tentando descobrir o porquê, mas tenho medo de desistir.

Isso, por sua vez, significa que não estou tão confiante sobre o commit 66c5df14c5f7e. Em particular, não tenho certeza sobre a mudança para lib/rspec/rails/adapters.rb. Mudei o nome da variável de instância de @running_example para @example no método "method_name", mas nem sei o que esse método faz, e as especificações/recursos passam independentemente de como a variável de instância é chamada.

Então, dê uma olhada nos commits e talvez você possa ver algo que eu não consigo que explique como fazer com que o Cucumber use a versão correta do rspec-core.

De qualquer forma, com esses commits podemos fazer coisas como:

RSpec.configure do |config|
  config.before :each do
    Capybara.current_driver = :celerity if example.options[:js]
  end
end

Há uma possível modificação adicional com a qual eu estava brincando, mas é um pouco mais invasiva, então ainda não cometi nada.

O problema é que em um bloco "before :all" obviamente não há "exemplo em execução" nesse ponto, portanto, se o usuário tentar fazer algo assim, ele será bombardeado porque "exemplo" é nil neste ponto:

config.before :all do
  do_something if example.options[:foo]
end

Nesse ponto, estamos executando dentro do escopo de uma classe RSpec::Core::ExampleGroup, portanto, se pedirmos "example", clicaremos no acessador "example" e obteremos nil de volta.

Observação:

config.before :all do
  # people could do it this way, and it works...
  do_something if self.class.metadata[:foo]
  # but I expect lots will try "example.options" and fail anyway...
end

A ideia com a qual eu estava brincando estava nesse contexto, retornando um objeto diferente de zero (provavelmente uma subclasse Metadata) que responde a "opções" e retornaria os metadados fornecidos pelo usuário passados ​​no nível do bloco de descrição. ou seja:

describe Thing, :js => true do
  before :all do
    example.options[:js] # => true
  end

  before :each do
    example.options[:foo] # => defined below
  end

  it "should behave", :foo => true do
  end
end

Assim, você pode definir seus metadados em qualquer nível e acessá-los em qualquer nível.

Não tenho certeza se isso está abrindo uma lata de worms, porque então teríamos que pensar em como esses metadados devem "gotejar" quando o aninhamento está envolvido, por exemplo:

describe Outer, :foo => 1 do
  describe Inner, :bar => 2 do
    before :all do
      # here should have access to :foo and :bar
    end

    before :each do
      # here should have access to :foo and :bar too
      # plus anything set at the "its" level
    end

    it "should behave", :foo => :override, :baz => 3 do
      # here :foo => override, :bar => 2, :baz => 3
    end
  end
end

Há muito o que revisar nesse comentário e estou pressionado pelo tempo neste minuto, mas re: a mudança de nome - queremos descontinuar running_example, pois há outros consumidores desse método à solta. Então, no rspec-core, algo como:

 exemplo de definição
 # código movido de running_example
 fim

 def running_example
 RSpec.deprecate("exemplo_running", "exemplo")
 exemplo
 fim

Então você não precisa se preocupar com as outras bibliotecas ainda e elas podem se mover de forma independente.

Ah, sim, boa ideia. Eu tinha visto o "RSpec.deprecate" em alguns lugares na base de código, mas não pensei em usá-lo.

Adicionado o aviso de descontinuação conforme sugerido:

http://github.com/wincent/rspec-core/commit/272c3762fa0af775f0c7d23affa3196bb7ad81c

Combinado - obrigado!!!!!

Apenas para registro, já que esse problema é o que encontrei usando a consulta do mecanismo de pesquisa "rspec metadata antes" e como não consegui encontrá-lo documentado em nenhum lugar.

Esse comportamento pode ser obtido em uma versão rspec atualizada da seguinte maneira:

before do |example|
  example.metadata[:foo] # => true
end

it "should behave", :foo => true do
end
Esta página foi útil?
0 / 5 - 0 avaliações